Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
73b3871
feat: add norisk client source support (#6707)
orb1n Jul 13, 2026
3ac3b95
changelog
Prospector Jul 13, 2026
39b4cf6
feat: implement depends on search filter
tdgao Jul 13, 2026
b574d32
Merge branch 'main' into truman/dependents-search-in-discovery
tdgao Jul 14, 2026
94f74b6
feat: add modpack "includes content" filter and update depends on
tdgao Jul 14, 2026
1f66ea1
feat: style pass
tdgao Jul 14, 2026
5c0061a
Merge branch 'main' into truman/dependents-search-in-discovery
tdgao Jul 27, 2026
26b90ae
feat: show included modpack projects filter tag better
tdgao Jul 27, 2026
63741f6
pnpm prepr
tdgao Jul 27, 2026
21c11b8
Merge branch 'main' into truman/dependents-search-in-discovery
tdgao Aug 14, 2026
e2d5d4d
feat: polish depends on filter
tdgao Aug 14, 2026
ee74727
fix: clear button, page scroll, and metadata loading state
tdgao Aug 14, 2026
3ce9c6d
feat: hook up excludes modpack
tdgao Aug 14, 2026
e51529c
fix: combobox closing race
tdgao Aug 14, 2026
76dab13
Revert "fix: combobox closing race"
tdgao Aug 14, 2026
ca37f94
fix scrolling after applying depends on
tdgao Aug 14, 2026
4101d01
Merge branch 'main' into truman/dependents-search-in-discovery
tdgao Aug 20, 2026
71260bf
feedback
tdgao Aug 20, 2026
1dd1804
pnpm prepr
tdgao Aug 20, 2026
79a3b1f
slightly make buttons larger for touch devices
tdgao Aug 20, 2026
0e8cba7
feat: add view dependents/view modpacks in project overflow menu
tdgao Aug 20, 2026
0bae0ae
pnpm prepr
tdgao Aug 20, 2026
65dfe63
fix: divider
tdgao Aug 20, 2026
73701c7
add icon
tdgao Aug 20, 2026
ed2c287
Merge of #6713
mergify[bot] Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/frontend/src/locales/en-US/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -3494,6 +3494,15 @@
"project.actions.servers-promo.title": {
"message": "Create a server"
},
"project.actions.view-dependents": {
"message": "View dependents"
},
"project.actions.view-modpacks": {
"message": "View modpacks"
},
"project.actions.view-project-type-dependents": {
"message": "View {projectType} dependents"
},
"project.ai-image-warning-modal.body.1": {
"message": "Using AI-generated images to represent your project is not allowed. Attempting to work around detection may lead to your Modrinth account being suspended."
},
Expand Down
79 changes: 78 additions & 1 deletion apps/frontend/src/pages/[type]/[project].vue
Original file line number Diff line number Diff line change
Expand Up @@ -564,10 +564,12 @@ import {
HeartIcon,
LeftArrowIcon,
MoreVerticalIcon,
PackageSearchIcon,
PlayIcon,
ReportIcon,
ScaleIcon,
ScanEyeIcon,
SearchIcon,
ServerPlusIcon,
SettingsIcon,
XIcon,
Expand All @@ -582,6 +584,8 @@ import {
ButtonLink,
commonMessages,
defineMessages,
formatDependencyProjectFilterOption,
formatProjectTypeSentence,
getActiveDisclosures,
IconButton,
injectModrinthClient,
Expand Down Expand Up @@ -846,6 +850,18 @@ const messages = defineMessages({
id: 'project.actions.review-project',
defaultMessage: 'Review project',
},
viewDependents: {
id: 'project.actions.view-dependents',
defaultMessage: 'View dependents',
},
viewProjectTypeDependents: {
id: 'project.actions.view-project-type-dependents',
defaultMessage: 'View {projectType} dependents',
},
viewModpacks: {
id: 'project.actions.view-modpacks',
defaultMessage: 'View modpacks',
},
rescanModpack: {
id: 'project.actions.rescan-modpack',
defaultMessage: 'Rescan modpack',
Expand Down Expand Up @@ -1904,6 +1920,30 @@ async function checkModpackArchives() {

const projectHeaderMoreActions = computed(() => {
const isStaff = !!(auth.value.user && tags.value.staffRoles.includes(auth.value.user.role))
const projectId = project.value?.id
const dependentSearchTypes = getDependentSearchTypes()
const dependentSearchActions = dependentSearchTypes
.filter((projectType) => projectType !== 'modpack')
.map((projectType) => ({
id: `view-${projectType}-dependents`,
label: formatMessage(
dependentSearchTypes.length === 1
? messages.viewDependents
: messages.viewProjectTypeDependents,
{
projectType: formatProjectTypeSentence(formatMessage, projectType),
},
),
icon: SearchIcon,
type: 'link',
to: {
path: `/discover/${projectType}s`,
query: {
dep: formatDependencyProjectFilterOption(projectId, ['required']),
},
},
}))
const isPluginOnly = dependentSearchTypes.length === 1 && dependentSearchTypes[0] === 'plugin'

return [
{
Expand All @@ -1914,7 +1954,21 @@ const projectHeaderMoreActions = computed(() => {
to: `${projectPath.value}/settings/analytics`,
shown: !!auth.value.user && !!currentMember.value,
},
{ type: 'divider', shown: !!auth.value.user && !!currentMember.value },
...dependentSearchActions,
{
id: 'view-modpacks',
label: formatMessage(messages.viewModpacks),
icon: PackageSearchIcon,
type: 'link',
to: {
path: '/discover/modpacks',
query: {
dep: formatDependencyProjectFilterOption(projectId, ['required']),
},
},
shown: !isPluginOnly && project.value?.actualProjectType !== 'modpack',
},
{ type: 'divider' },
{
id: 'moderation-checklist',
label: formatMessage(messages.reviewProject),
Expand Down Expand Up @@ -1977,6 +2031,29 @@ const projectHeaderMoreActions = computed(() => {
]
})

function getDependentSearchTypes() {
if (!project.value) return []

if (project.value.actualProjectType !== 'mod') {
return [isServerProject.value ? 'server' : project.value.actualProjectType]
}

const loaders = project.value.loaders ?? []
const projectTypes = []

if (loaders.some((loader) => tags.value.loaderData.modLoaders.includes(loader))) {
projectTypes.push('mod')
}
if (loaders.some((loader) => tags.value.loaderData.allPluginLoaders.includes(loader))) {
projectTypes.push('plugin')
}
if (loaders.some((loader) => tags.value.loaderData.dataPackLoaders.includes(loader))) {
projectTypes.push('datapack')
}

return projectTypes.length > 0 ? projectTypes : ['mod']
}

const createCanonicalUrl = () =>
project.value ? `https://modrinth.com/project/${project.value.id}` : undefined

Expand Down
2 changes: 2 additions & 0 deletions packages/assets/generated-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ import _PackageIcon from './icons/package.svg?component'
import _PackageClosedIcon from './icons/package-closed.svg?component'
import _PackageOpenIcon from './icons/package-open.svg?component'
import _PackagePlusIcon from './icons/package-plus.svg?component'
import _PackageSearchIcon from './icons/package-search.svg?component'
import _PageRoundIcon from './icons/page-round.svg?component'
import _PaintbrushIcon from './icons/paintbrush.svg?component'
import _PaletteIcon from './icons/palette.svg?component'
Expand Down Expand Up @@ -656,6 +657,7 @@ export const PackageIcon = _PackageIcon
export const PackageClosedIcon = _PackageClosedIcon
export const PackageOpenIcon = _PackageOpenIcon
export const PackagePlusIcon = _PackagePlusIcon
export const PackageSearchIcon = _PackageSearchIcon
export const PageRoundIcon = _PageRoundIcon
export const PaintbrushIcon = _PaintbrushIcon
export const PaletteIcon = _PaletteIcon
Expand Down
20 changes: 20 additions & 0 deletions packages/assets/icons/package-search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions packages/ui/src/components/base/Combobox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
:name="searchName"
:placeholder="searchPlaceholder || placeholder"
:disabled="disabled"
:clearable="clearable"
:autocomplete="searchAutocomplete"
:autocorrect="searchAutocorrect"
:autocapitalize="searchAutocapitalize"
Expand All @@ -38,6 +39,7 @@
@focusin="handleSearchFocus"
@focusout="handleSearchFocusout"
@click="handleSearchClick"
@clear="handleSearchClear"
>
<template v-if="showChevron" #right>
<ChevronLeftIcon
Expand Down Expand Up @@ -274,6 +276,7 @@ const props = withDefaults(
placeholder?: string
disabled?: boolean
searchable?: boolean
clearable?: boolean
searchPlaceholder?: string
listbox?: boolean
showChevron?: boolean
Expand Down Expand Up @@ -316,6 +319,7 @@ const props = withDefaults(
placeholder: 'Select an option',
disabled: false,
searchable: false,
clearable: false,
searchPlaceholder: 'Search...',
listbox: true,
showChevron: true,
Expand Down Expand Up @@ -838,6 +842,12 @@ function handleSearchInput() {
}
}

function handleSearchClear() {
userHasTyped.value = true
emit('searchInput', searchQuery.value)
closeDropdown()
}

function handleSearchFocus(event: FocusEvent) {
const target = event.target
if (props.selectSearchTextOnFocus && target instanceof HTMLInputElement) {
Expand Down
28 changes: 25 additions & 3 deletions packages/ui/src/components/project/ProjectCombobox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
:no-options-message="searchLoading ? loadingMessage : noResultsMessage"
:disable-search-filter="true"
:disabled="disabled"
:clearable="clearable"
:sync-with-selection="syncWithSelection"
:select-search-text-on-focus="selectSearchTextOnFocus"
show-icon-in-selected
@search-input="(query) => handleSearch(query)"
/>
Expand All @@ -32,12 +35,13 @@ export type ProjectType =
| 'plugin'
| 'server'

interface SearchHit {
export interface SearchHit {
project_id: string
title: string
icon_url?: string
project_type: string
slug: string
author?: string
}

const props = withDefaults(
Expand All @@ -54,6 +58,12 @@ const props = withDefaults(
noResultsMessage?: string
/** Whether the combobox is disabled */
disabled?: boolean
/** Whether to show a button for clearing the search input */
clearable?: boolean
/** Keep the selected project's label in the search input */
syncWithSelection?: boolean
/** Select all search text when the input receives focus */
selectSearchTextOnFocus?: boolean
/** Maximum number of results to show */
limit?: number
/** Project IDs to exclude from results */
Expand All @@ -69,6 +79,7 @@ const props = withDefaults(
loadingMessage: 'Loading...',
noResultsMessage: 'No results found',
disabled: false,
syncWithSelection: true,
limit: 20,
},
)
Expand All @@ -83,6 +94,10 @@ const searchResultsCache = ref<Map<string, SearchHit>>(new Map())

const { labrinth } = injectModrinthClient()

function isAllowedProjectType(projectType: string): boolean {
return !props.projectTypes || props.projectTypes.includes(projectType as ProjectType)
}

const userProjectHits = ref<SearchHit[]>([])
const userProjectsFuse = ref<Fuse<SearchHit> | null>(null)

Expand Down Expand Up @@ -163,7 +178,7 @@ watch(
} else {
try {
const project = await labrinth.projects_v2.get(newId)
if (project) {
if (project && isAllowedProjectType(project.project_type)) {
hit = {
project_id: project.id,
title: project.title,
Expand All @@ -178,6 +193,9 @@ watch(
return
}
}
if (hit && !isAllowedProjectType(hit.project_type)) {
hit = null
}

selectedProject.value = hit
if (hit && !options.value.some((o) => o.value === hit!.project_id)) {
Expand Down Expand Up @@ -220,7 +238,11 @@ const search = async (query: string) => {
const uniqueHits: SearchHit[] = []

for (const hit of allHits) {
if (!seenIds.has(hit.project_id) && !excludeSet.has(hit.project_id)) {
if (
isAllowedProjectType(hit.project_type) &&
!seenIds.has(hit.project_id) &&
!excludeSet.has(hit.project_id)
) {
seenIds.add(hit.project_id)
uniqueHits.push(hit)
searchResultsCache.value.set(hit.project_id, hit)
Expand Down
Loading
Loading