fix: open modrinth project links in the app (#6072)
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import { Intercom, shutdown as shutdownIntercom } from '@intercom/messenger-js-sdk'
|
import { Intercom, shutdown as shutdownIntercom } from '@intercom/messenger-js-sdk'
|
||||||
import {
|
import {
|
||||||
AuthFeature,
|
AuthFeature,
|
||||||
|
ModrinthApiError,
|
||||||
NodeAuthFeature,
|
NodeAuthFeature,
|
||||||
nodeAuthState,
|
nodeAuthState,
|
||||||
PanelVersionFeature,
|
PanelVersionFeature,
|
||||||
@@ -98,6 +99,7 @@ import { command_listener, warning_listener } from '@/helpers/events.js'
|
|||||||
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts'
|
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts'
|
||||||
import { create_profile_and_install_from_file } from '@/helpers/pack'
|
import { create_profile_and_install_from_file } from '@/helpers/pack'
|
||||||
import { list } from '@/helpers/profile.js'
|
import { list } from '@/helpers/profile.js'
|
||||||
|
import { mergeUrlQuery, parseModrinthLink } from '@/helpers/project-links.ts'
|
||||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||||
import {
|
import {
|
||||||
@@ -1028,6 +1030,28 @@ async function installUpdate() {
|
|||||||
}, 250)
|
}, 250)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openModrinthProjectLinkInApp(parsed) {
|
||||||
|
const { slug, pathSuffix, url } = parsed
|
||||||
|
const loadToken = loading.begin()
|
||||||
|
try {
|
||||||
|
const { id } = await tauriApiClient.labrinth.projects_v2.check(slug)
|
||||||
|
const query = mergeUrlQuery(route.query, url)
|
||||||
|
await router.push({
|
||||||
|
path: `/project/${id}${pathSuffix}`,
|
||||||
|
query,
|
||||||
|
hash: url.hash || undefined,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ModrinthApiError && err.statusCode === 404) {
|
||||||
|
openUrl(url.href)
|
||||||
|
} else {
|
||||||
|
handleError(err)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.end(loadToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleClick(e) {
|
function handleClick(e) {
|
||||||
let target = e.target
|
let target = e.target
|
||||||
while (target != null) {
|
while (target != null) {
|
||||||
@@ -1040,7 +1064,12 @@ function handleClick(e) {
|
|||||||
!target.href.startsWith('https://tauri.localhost') &&
|
!target.href.startsWith('https://tauri.localhost') &&
|
||||||
!target.href.startsWith('http://tauri.localhost')
|
!target.href.startsWith('http://tauri.localhost')
|
||||||
) {
|
) {
|
||||||
openUrl(target.href)
|
const parsed = parseModrinthLink(target.href)
|
||||||
|
if (parsed) {
|
||||||
|
void openModrinthProjectLinkInApp(parsed)
|
||||||
|
} else {
|
||||||
|
openUrl(target.href)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
break
|
break
|
||||||
|
|||||||
83
apps/app-frontend/src/helpers/project-links.ts
Normal file
83
apps/app-frontend/src/helpers/project-links.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import type { LocationQuery, LocationQueryRaw } from 'vue-router'
|
||||||
|
|
||||||
|
const MODRINTH_HOSTNAMES = new Set(['modrinth.com', 'www.modrinth.com'])
|
||||||
|
|
||||||
|
const SUPPORTED_PROJECT_TYPES = new Set([
|
||||||
|
'mod',
|
||||||
|
'modpack',
|
||||||
|
'resourcepack',
|
||||||
|
'datapack',
|
||||||
|
'plugin',
|
||||||
|
'shader',
|
||||||
|
'server',
|
||||||
|
'project',
|
||||||
|
])
|
||||||
|
|
||||||
|
export function parseModrinthLink(
|
||||||
|
href: string,
|
||||||
|
): { slug: string; pathSuffix: string; url: URL } | null {
|
||||||
|
let url: URL
|
||||||
|
try {
|
||||||
|
url = new URL(href)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MODRINTH_HOSTNAMES.has(url.hostname.toLowerCase())) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = url.pathname.split('/').filter((p) => p.length > 0)
|
||||||
|
if (segments.length < 2) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SUPPORTED_PROJECT_TYPES.has(segments[0].toLowerCase())) {
|
||||||
|
const slug = segments[1]
|
||||||
|
if (!slug) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const rest: string[] = segments.slice(2)
|
||||||
|
const pathSuffix = toValidAppSubpath(rest)
|
||||||
|
if (pathSuffix === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return { slug, pathSuffix, url }
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SUPPORTED_SUBPATHS = ['versions', 'gallery']
|
||||||
|
|
||||||
|
function toValidAppSubpath(rest: string[]): string | null {
|
||||||
|
if (rest.length === 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const subroute = rest[0].toLowerCase()
|
||||||
|
if (rest.length === 1 && SUPPORTED_SUBPATHS.includes(subroute)) {
|
||||||
|
return `/${subroute}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rest.length === 2 && subroute === 'version') {
|
||||||
|
return `/version/${rest[1]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeUrlQuery(routeQuery: LocationQuery, linkUrl: URL): LocationQueryRaw {
|
||||||
|
const newQuery: LocationQueryRaw = { ...routeQuery }
|
||||||
|
const keys = new Set<string>()
|
||||||
|
linkUrl.searchParams.forEach((_value, key) => {
|
||||||
|
keys.add(key)
|
||||||
|
})
|
||||||
|
for (const key of keys) {
|
||||||
|
const values = linkUrl.searchParams.getAll(key)
|
||||||
|
newQuery[key] = values.length === 1 ? values[0] : values
|
||||||
|
}
|
||||||
|
return newQuery
|
||||||
|
}
|
||||||
@@ -26,6 +26,23 @@ export class LabrinthProjectsV2Module extends AbstractModule {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a project slug or ID exists and return its canonical project ID.
|
||||||
|
*
|
||||||
|
* @param idOrSlug - Project ID or slug (e.g. `sodium` or `AANobbMI`)
|
||||||
|
*/
|
||||||
|
public async check(idOrSlug: string): Promise<Labrinth.Projects.v2.ProjectCheckResponse> {
|
||||||
|
const encoded = encodeURIComponent(idOrSlug)
|
||||||
|
return this.client.request<Labrinth.Projects.v2.ProjectCheckResponse>(
|
||||||
|
`/project/${encoded}/check`,
|
||||||
|
{
|
||||||
|
api: 'labrinth',
|
||||||
|
version: 2,
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get multiple projects by IDs
|
* Get multiple projects by IDs
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -468,6 +468,10 @@ export namespace Labrinth {
|
|||||||
monetization_status: MonetizationStatus
|
monetization_status: MonetizationStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProjectCheckResponse = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
export type SearchResultHit = {
|
export type SearchResultHit = {
|
||||||
project_id: string
|
project_id: string
|
||||||
project_type: ProjectType
|
project_type: ProjectType
|
||||||
|
|||||||
Reference in New Issue
Block a user