mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 09:04:55 +00:00
bunch more fixes lol
This commit is contained in:
@@ -433,7 +433,7 @@ function dismissInfoBanner() {
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
<template #actions>
|
||||
<template v-if="false" #actions>
|
||||
<div class="flex">
|
||||
<ButtonStyled color="blue">
|
||||
<a> {{ formatMessage(messages.learnMore) }} <RightArrowIcon /> </a>
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
<VersionPage
|
||||
:version="version"
|
||||
:enrichment="enrichment"
|
||||
:enrichment-loading="dependenciesLoading"
|
||||
:members="members"
|
||||
:user-link-creator="(user) => (moderator ? `/user/${user.id}` : undefined)"
|
||||
:dependency-link-creator="createDependencyLink"
|
||||
@@ -400,7 +401,7 @@
|
||||
</pre
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-else-if="showVersionSkeleton">
|
||||
<div class="flex flex-col gap-4 pb-[30rem]">
|
||||
<div
|
||||
class="mt-4 flex h-[8rem] w-full animate-pulse items-center justify-center rounded-2xl bg-surface-3"
|
||||
@@ -466,10 +467,12 @@ import {
|
||||
} from '@modrinth/ui'
|
||||
import { isStaff } from '@modrinth/utils'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { onServerPrefetch } from 'vue'
|
||||
|
||||
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
|
||||
import { getSignInRouteObj } from '~/composables/auth.js'
|
||||
import { STALE_TIME } from '~/composables/queries/project'
|
||||
import { projectQueryOptions, STALE_TIME } from '~/composables/queries/project'
|
||||
import { versionQueryOptions } from '~/composables/queries/version'
|
||||
import { createDataPackVersion } from '~/helpers/package.js'
|
||||
import { reportVersion } from '~/utils/report-helpers.ts'
|
||||
|
||||
@@ -499,6 +502,7 @@ const {
|
||||
versionsLoading,
|
||||
loadVersions,
|
||||
dependencies: contextDependencies,
|
||||
dependenciesLoading,
|
||||
loadDependencies,
|
||||
invalidate,
|
||||
cdnDownloadReason,
|
||||
@@ -522,26 +526,31 @@ const signInRouteObj = computed(() => getSignInRouteObj(route))
|
||||
const versionRouteParam = computed(() => route.params.version as string)
|
||||
const isLatestRoute = computed(() => versionRouteParam.value === 'latest')
|
||||
|
||||
function filterVersionsForLatestRoute(allVersions: Labrinth.Versions.v3.Version[]) {
|
||||
let filtered = allVersions
|
||||
|
||||
const loaderFilter = route.query.loader
|
||||
if (typeof loaderFilter === 'string') {
|
||||
filtered = filtered.filter((x) => x.loaders.includes(loaderFilter))
|
||||
}
|
||||
|
||||
const gameVersionFilter = route.query.version
|
||||
if (typeof gameVersionFilter === 'string') {
|
||||
filtered = filtered.filter((x) => x.game_versions.includes(gameVersionFilter))
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
const latestVersionId = computed(() => {
|
||||
if (!isLatestRoute.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
let allVersions = versions.value ?? []
|
||||
const filtered = filterVersionsForLatestRoute(versions.value ?? [])
|
||||
if (filtered.length === 0) return null
|
||||
|
||||
const loaderFilter = route.query.loader
|
||||
if (typeof loaderFilter === 'string') {
|
||||
allVersions = allVersions.filter((x) => x.loaders.includes(loaderFilter))
|
||||
}
|
||||
|
||||
const gameVersionFilter = route.query.version
|
||||
if (typeof gameVersionFilter === 'string') {
|
||||
allVersions = allVersions.filter((x) => x.game_versions.includes(gameVersionFilter))
|
||||
}
|
||||
|
||||
if (allVersions.length === 0) return null
|
||||
|
||||
return allVersions.reduce((a, b) => (a.date_published > b.date_published ? a : b)).id
|
||||
return filtered.reduce((a, b) => (a.date_published > b.date_published ? a : b)).id
|
||||
})
|
||||
|
||||
const versionLookupKey = computed(() =>
|
||||
@@ -552,6 +561,7 @@ const {
|
||||
data: version,
|
||||
refetch: refetchVersion,
|
||||
error: versionError,
|
||||
isPending: versionPending,
|
||||
} = useQuery({
|
||||
queryKey: computed(
|
||||
() => ['project', project.value.id, 'version', 'v3', versionLookupKey.value] as const,
|
||||
@@ -562,6 +572,30 @@ const {
|
||||
staleTime: STALE_TIME,
|
||||
})
|
||||
|
||||
const showVersionSkeleton = computed(() => import.meta.client && versionPending.value)
|
||||
|
||||
onServerPrefetch(async () => {
|
||||
if (!project.value.id) return
|
||||
|
||||
let lookupKey = versionRouteParam.value
|
||||
|
||||
if (isLatestRoute.value) {
|
||||
loadVersions()
|
||||
const versionsData = await queryClient.ensureQueryData(
|
||||
projectQueryOptions.versionsV3(project.value.id, client),
|
||||
)
|
||||
const filtered = filterVersionsForLatestRoute(versionsData ?? [])
|
||||
if (filtered.length === 0) return
|
||||
lookupKey = filtered.reduce((a, b) => (a.date_published > b.date_published ? a : b)).id
|
||||
}
|
||||
|
||||
if (!lookupKey || lookupKey === 'latest') return
|
||||
|
||||
await queryClient.ensureQueryData(
|
||||
versionQueryOptions.fromProject(project.value.id, lookupKey, client),
|
||||
)
|
||||
})
|
||||
|
||||
watch(
|
||||
versionError,
|
||||
(error) => {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<div class="group relative float-end ml-4">
|
||||
<OverflowMenu
|
||||
v-tooltip="formatMessage(messages.editIconButton)"
|
||||
:dropdown-id="`${baseId}-edit-icon`"
|
||||
class="m-0 cursor-pointer appearance-none border-none bg-transparent p-0 transition-transform group-active:scale-95"
|
||||
:options="[
|
||||
{
|
||||
@@ -138,7 +139,7 @@
|
||||
</NewModal>
|
||||
<NormalPage :sidebar="cosmetics.leftContentLayout ? 'left' : 'right'">
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<ClientOnly>
|
||||
<nuxt-link
|
||||
v-if="returnLink"
|
||||
@@ -201,19 +202,32 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 flex gap-2 sm:col-span-1">
|
||||
<div class="col-span-2 flex items-center gap-2 sm:col-span-1">
|
||||
<template v-if="canEdit">
|
||||
<ButtonStyled>
|
||||
<ButtonStyled size="large">
|
||||
<button @click="openEditModal">
|
||||
<EditIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.editButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red" color-fill="text">
|
||||
<button @click="() => $refs.deleteModal.show()">
|
||||
<TrashIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.deleteLabel) }}
|
||||
</button>
|
||||
<ButtonStyled size="large" circular type="transparent">
|
||||
<OverflowMenu
|
||||
:dropdown-id="`${baseId}-more-options`"
|
||||
:options="[
|
||||
{
|
||||
id: 'delete',
|
||||
color: 'red',
|
||||
action: () => deleteModal?.show(),
|
||||
},
|
||||
]"
|
||||
:aria-label="formatMessage(commonMessages.moreOptionsButton)"
|
||||
>
|
||||
<MoreVerticalIcon aria-hidden="true" />
|
||||
<template #delete>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.deleteLabel) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</div>
|
||||
@@ -226,7 +240,12 @@
|
||||
v-if="collection.description"
|
||||
:title="formatMessage(commonMessages.descriptionLabel)"
|
||||
>
|
||||
<p class="m-0 break-words">{{ collection.description }}</p>
|
||||
<div
|
||||
v-if="supportsMarkdown"
|
||||
class="description-body"
|
||||
v-html="renderString(collection.description)"
|
||||
/>
|
||||
<p v-else class="m-0 break-words">{{ collection.description }}</p>
|
||||
</SidebarCard>
|
||||
<SidebarCard
|
||||
v-if="collection.id !== 'following'"
|
||||
@@ -238,9 +257,7 @@
|
||||
>
|
||||
<Avatar :src="creator.avatar_url" :alt="creator.username" size="32px" circle />
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="grid w-full grid-cols-[1fr_auto] flex-nowrap items-center gap-1 group-hover:underline"
|
||||
>
|
||||
<span class="flex w-full flex-nowrap items-center gap-1 group-hover:underline">
|
||||
<span class="min-w-0 overflow-hidden truncate">{{ creator.username }}</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -373,6 +390,7 @@ import {
|
||||
HeartMinusIcon,
|
||||
LinkIcon,
|
||||
LockIcon,
|
||||
MoreVerticalIcon,
|
||||
SaveIcon,
|
||||
SpinnerIcon,
|
||||
TrashIcon,
|
||||
@@ -411,9 +429,10 @@ import {
|
||||
useSavable,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { isAdmin } from '@modrinth/utils'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { isAdmin, renderString } from '@modrinth/utils'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { onServerPrefetch } from 'vue'
|
||||
|
||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||
|
||||
@@ -431,6 +450,32 @@ const route = useNativeRoute()
|
||||
const router = useRouter()
|
||||
const auth = await useAuth()
|
||||
const cosmetics = useCosmetics()
|
||||
const queryClient = useQueryClient()
|
||||
const baseId = useId()
|
||||
|
||||
async function fetchProjectsByIds(projectIds) {
|
||||
const segmentSize = 800
|
||||
const segments = []
|
||||
for (let i = 0; i < projectIds.length; i += segmentSize) {
|
||||
segments.push(projectIds.slice(i, i + segmentSize))
|
||||
}
|
||||
const results = await Promise.all(
|
||||
segments.map((ids) => api.labrinth.projects_v2.getMultiple(ids)),
|
||||
)
|
||||
const projects = results.flat()
|
||||
for (const project of projects) {
|
||||
project.categories = project.categories.concat(project.loaders)
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
async function fetchFollowedProjects(userId) {
|
||||
const projects = await api.labrinth.users_v2.getFollowedProjects(userId)
|
||||
for (const project of projects) {
|
||||
project.categories = project.categories.concat(project.loaders)
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
collectionDescription: {
|
||||
@@ -595,6 +640,8 @@ const creator = computed(() =>
|
||||
isFollowingCollection.value ? auth.value.user : fetchedCreator.value,
|
||||
)
|
||||
|
||||
const supportsMarkdown = computed(() => creator.value?.id === '2REoufqX')
|
||||
|
||||
// Query for followed projects
|
||||
const {
|
||||
data: followedProjects,
|
||||
@@ -602,13 +649,7 @@ const {
|
||||
isFetching: followedProjectsIsFetching,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => ['user', auth.value.user?.id, 'follows']),
|
||||
queryFn: async () => {
|
||||
const projects = await api.labrinth.users_v2.getFollowedProjects(auth.value.user.id)
|
||||
for (const project of projects) {
|
||||
project.categories = project.categories.concat(project.loaders)
|
||||
}
|
||||
return projects
|
||||
},
|
||||
queryFn: async () => fetchFollowedProjects(auth.value.user.id),
|
||||
enabled: computed(() => isFollowingCollection.value && !!auth.value.user?.id),
|
||||
placeholderData: [],
|
||||
})
|
||||
@@ -620,22 +661,7 @@ const {
|
||||
isFetching: collectionProjectsIsFetching,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => ['projects', collection.value?.projects]),
|
||||
queryFn: async () => {
|
||||
const projectIds = collection.value.projects
|
||||
const segmentSize = 800
|
||||
const segments = []
|
||||
for (let i = 0; i < projectIds.length; i += segmentSize) {
|
||||
segments.push(projectIds.slice(i, i + segmentSize))
|
||||
}
|
||||
const results = await Promise.all(
|
||||
segments.map((ids) => api.labrinth.projects_v2.getMultiple(ids)),
|
||||
)
|
||||
const projects = results.flat()
|
||||
for (const project of projects) {
|
||||
project.categories = project.categories.concat(project.loaders)
|
||||
}
|
||||
return projects
|
||||
},
|
||||
queryFn: () => fetchProjectsByIds(collection.value.projects),
|
||||
enabled: computed(() => !isFollowingCollection.value && !!collection.value?.projects?.length),
|
||||
placeholderData: [],
|
||||
})
|
||||
@@ -647,12 +673,48 @@ const projects = computed(() =>
|
||||
|
||||
// Loading state
|
||||
const isLoading = computed(() => {
|
||||
if (!import.meta.client) return false
|
||||
|
||||
if (isFollowingCollection.value) {
|
||||
return followedProjectsIsFetching.value
|
||||
}
|
||||
return collectionIsPending.value || creatorIsPending.value || collectionProjectsIsFetching.value
|
||||
})
|
||||
|
||||
onServerPrefetch(async () => {
|
||||
if (isFollowingCollection.value) {
|
||||
const userId = auth.value.user?.id
|
||||
if (!userId) return
|
||||
|
||||
await queryClient.ensureQueryData({
|
||||
queryKey: ['user', userId, 'follows'],
|
||||
queryFn: () => fetchFollowedProjects(userId),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!collectionId) return
|
||||
|
||||
const collectionData = await queryClient.ensureQueryData({
|
||||
queryKey: ['collection', collectionId],
|
||||
queryFn: () => api.labrinth.collections.get(collectionId),
|
||||
})
|
||||
|
||||
if (collectionData?.user) {
|
||||
await queryClient.ensureQueryData({
|
||||
queryKey: ['user', collectionData.user],
|
||||
queryFn: () => api.labrinth.users_v2.get(collectionData.user),
|
||||
})
|
||||
}
|
||||
|
||||
if (collectionData?.projects?.length) {
|
||||
await queryClient.ensureQueryData({
|
||||
queryKey: ['projects', collectionData.projects],
|
||||
queryFn: () => fetchProjectsByIds(collectionData.projects),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
[collection, creator],
|
||||
([col, cre]) => {
|
||||
@@ -716,6 +778,7 @@ const showUpdatedDate = computed(() => {
|
||||
})
|
||||
|
||||
const editModal = ref(null)
|
||||
const deleteModal = ref(null)
|
||||
const iconInputRef = ref(null)
|
||||
const icon = ref(null)
|
||||
const deletedIcon = ref(false)
|
||||
@@ -881,4 +944,15 @@ function openEditModal(event) {
|
||||
// Omorphia's dropdowns are harcoded in width, so we need to override that
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
:deep(.description-body) {
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -146,9 +146,8 @@
|
||||
<div class="normal-page__sidebar">
|
||||
<AdPlaceholder v-if="!auth.user" />
|
||||
|
||||
<div class="card flex-card">
|
||||
<h2>Members</h2>
|
||||
<div class="details-list">
|
||||
<SidebarCard title="Members">
|
||||
<div class="flex flex-col gap-3 font-semibold">
|
||||
<nuxt-link
|
||||
v-for="member in acceptedMembers"
|
||||
:key="`member-${member?.user?.id}`"
|
||||
@@ -163,22 +162,20 @@
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<span class="flex w-full flex-nowrap items-center gap-1 group-hover:underline">
|
||||
<span class="min-w-0 overflow-hidden truncate font-normal text-contrast">{{
|
||||
member.user.username
|
||||
}}</span>
|
||||
<span class="min-w-0 overflow-hidden truncate">{{ member.user.username }}</span>
|
||||
<CrownIcon
|
||||
v-if="member.is_owner"
|
||||
v-tooltip="'Organization owner'"
|
||||
class="text-brand-orange"
|
||||
/>
|
||||
</span>
|
||||
<span class="text-sm font-normal">
|
||||
<span class="text-sm font-normal text-secondary">
|
||||
{{ member?.role ? member.role : 'Member' }}
|
||||
</span>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarCard>
|
||||
</div>
|
||||
<div class="normal-page__content">
|
||||
<div v-if="isInvited" class="universal-card information invited">
|
||||
@@ -317,6 +314,7 @@ import {
|
||||
PROJECT_DEP_MARKER_QUERY,
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
SidebarCard,
|
||||
useCompactNumber,
|
||||
useFormatNumber,
|
||||
useVIntl,
|
||||
@@ -774,4 +772,8 @@ async function copyPermalink() {
|
||||
.popout-checkbox {
|
||||
padding: var(--gap-sm) var(--gap-md);
|
||||
}
|
||||
|
||||
.new-page {
|
||||
column-gap: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
v-if="organizations?.length > 0"
|
||||
class="mb-4 rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4 pt-3"
|
||||
>
|
||||
<h2 class="m-0 mb-2 text-lg text-contrast">
|
||||
<h2 class="m-0 mb-2 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.profileOrganizations) }}
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@@ -1040,4 +1040,8 @@ export default defineNuxtComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-page {
|
||||
column-gap: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user