mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
Merge remote-tracking branch 'origin/main' into prospector/disclosures-frontend
# Conflicts: # apps/frontend/src/pages/[type]/[project].vue # apps/frontend/src/pages/[type]/[project]/settings/tags.vue
This commit is contained in:
@@ -111,6 +111,18 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.align-x {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&.align-l {
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
|
||||
&.align-r {
|
||||
margin: 0 auto 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.normal-page__sidebar {
|
||||
@@ -140,6 +152,18 @@
|
||||
'sidebar'
|
||||
/ 100%;
|
||||
|
||||
&.align-x {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&.align-l {
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
|
||||
&.align-r {
|
||||
margin: 0 auto 0 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
&.sidebar {
|
||||
grid-template:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="flex h-full appearance-none flex-col gap-2.5 rounded-2xl border border-solid p-5 px-4 text-left transition-colors sm:gap-4"
|
||||
:class="{
|
||||
'cursor-not-allowed border-surface-5 bg-surface-2 opacity-60': disabled,
|
||||
'cursor-default border-brand bg-highlight-green': !disabled && active,
|
||||
'cursor-default border-brand bg-brand-highlight': !disabled && active,
|
||||
'border-surface-5 bg-surface-3 hover:bg-surface-4 active:scale-95': !disabled && !active,
|
||||
}"
|
||||
:disabled="disabled"
|
||||
|
||||
@@ -82,10 +82,8 @@
|
||||
|
||||
<div
|
||||
v-if="selectedVersion && downloadDataLoaded"
|
||||
:role="compatibleVersions.length > 1 ? 'radiogroup' : undefined"
|
||||
:aria-label="
|
||||
compatibleVersions.length > 1 ? formatMessage(messages.compatibleVersionTitle) : undefined
|
||||
"
|
||||
:role="showVersionSelector ? 'radiogroup' : undefined"
|
||||
:aria-label="showVersionSelector ? formatMessage(messages.compatibleVersionTitle) : undefined"
|
||||
class="flex flex-col gap-2.5"
|
||||
>
|
||||
<h3
|
||||
@@ -102,26 +100,12 @@
|
||||
:download-reason="downloadReason"
|
||||
:current-game-version="currentGameVersion"
|
||||
:current-platform="currentPlatform"
|
||||
:selectable="compatibleVersions.length > 1"
|
||||
:selectable="showVersionSelector"
|
||||
:selected="compatibleVersion.id === selectedVersion.id"
|
||||
:show-download="
|
||||
compatibleVersions.length === 1 || compatibleVersion.id === selectedVersion.id
|
||||
"
|
||||
:color="
|
||||
compatibleVersion.id === selectedVersion.id &&
|
||||
compatibleVersions.length === 1 &&
|
||||
!hasAdditionalDownloads
|
||||
? 'brand'
|
||||
: 'standard'
|
||||
"
|
||||
:type="
|
||||
compatibleVersion.id === selectedVersion.id &&
|
||||
compatibleVersions.length === 1 &&
|
||||
!hasAdditionalDownloads
|
||||
? 'standard'
|
||||
: 'transparent'
|
||||
"
|
||||
:circular="hasAdditionalDownloads || compatibleVersions.length > 1"
|
||||
:show-download="!showVersionSelector || compatibleVersion.id === selectedVersion.id"
|
||||
:color="isPrimaryDownloadVersion(compatibleVersion) ? 'brand' : 'standard'"
|
||||
:type="isPrimaryDownloadVersion(compatibleVersion) ? 'standard' : 'transparent'"
|
||||
:circular="!isPrimaryDownloadVersion(compatibleVersion)"
|
||||
@select="selectCompatibleVersion(compatibleVersion)"
|
||||
@download="emit('download')"
|
||||
/>
|
||||
@@ -494,6 +478,10 @@ const hasAdditionalDownloads = computed(() => {
|
||||
return hrefs.size > 1
|
||||
})
|
||||
|
||||
const showVersionSelector = computed(
|
||||
() => hasAdditionalDownloads.value && compatibleVersions.value.length > 1,
|
||||
)
|
||||
|
||||
watch(
|
||||
[currentGameVersion, currentPlatform, selectedVersion, selectedPrimaryFile],
|
||||
() => {
|
||||
@@ -539,6 +527,10 @@ function selectCompatibleVersion(version: Labrinth.Versions.v3.Version) {
|
||||
userSelectedCompatibleVersionId.value = version.id
|
||||
}
|
||||
|
||||
function isPrimaryDownloadVersion(version: Labrinth.Versions.v3.Version) {
|
||||
return !hasAdditionalDownloads.value && version.id === defaultSelectedVersion.value?.id
|
||||
}
|
||||
|
||||
function latestVersionByType(type: Labrinth.Versions.v3.VersionChannel) {
|
||||
return filteredVersions.value
|
||||
.filter((version) => version.version_type === type)
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
class="border !border-solid !transition-all hover:bg-button-bgHover hover:no-underline"
|
||||
:class="
|
||||
selectedLoaders.includes(loader.name)
|
||||
? 'border-brand bg-highlight-green text-brand'
|
||||
? 'border-brand bg-brand-highlight text-brand'
|
||||
: 'border-surface-5'
|
||||
"
|
||||
:style="`--_color: var(--color-platform-${loader.name})`"
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
</div>
|
||||
<div class="flex w-full flex-row justify-stretch gap-2">
|
||||
<Button class="w-full text-contrast" @click="handleClose">
|
||||
{{ props.closeButtonText ?? formatMessage(messages.closeButton) }}
|
||||
{{ props.closeButtonText ?? formatMessage(commonMessages.closeButton) }}
|
||||
</Button>
|
||||
<Button
|
||||
type="colored"
|
||||
@@ -267,10 +267,6 @@ const messages = defineMessages({
|
||||
id: 'dashboard.creator-tax-form-modal.confirmation.download-button',
|
||||
defaultMessage: 'Download {formType}',
|
||||
},
|
||||
closeButton: {
|
||||
id: 'dashboard.creator-tax-form-modal.close-button',
|
||||
defaultMessage: 'Close',
|
||||
},
|
||||
incompleteTitle: {
|
||||
id: 'dashboard.creator-tax-form-modal.incomplete.title',
|
||||
defaultMessage: 'Tax form incomplete',
|
||||
|
||||
@@ -856,6 +856,10 @@ async function updateDetailStatus(
|
||||
|
||||
updatingDetails.add(detailId)
|
||||
|
||||
const previousMarkedCount = selectedFile.value
|
||||
? getFileMarkedCount(selectedFile.value)
|
||||
: undefined
|
||||
|
||||
try {
|
||||
await updateIssueDetails([{ detail_id: detailId, verdict }])
|
||||
|
||||
@@ -880,7 +884,7 @@ async function updateDetailStatus(
|
||||
if (verdict !== 'pending' && selectedFile.value) {
|
||||
const markedCount = getFileMarkedCount(selectedFile.value)
|
||||
const totalCount = getFileDetailCount(selectedFile.value)
|
||||
if (markedCount === totalCount) {
|
||||
if (previousMarkedCount != markedCount && markedCount === totalCount) {
|
||||
backToFileList()
|
||||
}
|
||||
}
|
||||
@@ -938,6 +942,10 @@ async function updateGlobalDetailStatus(
|
||||
|
||||
updatingGlobalDetailKeys.add(detail.key)
|
||||
|
||||
const previousMarkedCount = selectedFile.value
|
||||
? getFileMarkedCount(selectedFile.value)
|
||||
: undefined
|
||||
|
||||
try {
|
||||
await updateGlobalIssueDetail(detail.key, verdict)
|
||||
|
||||
@@ -958,7 +966,7 @@ async function updateGlobalDetailStatus(
|
||||
if (verdict !== 'pending' && selectedFile.value) {
|
||||
const markedCount = getFileMarkedCount(selectedFile.value)
|
||||
const totalCount = getFileDetailCount(selectedFile.value)
|
||||
if (markedCount === totalCount) {
|
||||
if (previousMarkedCount != markedCount && markedCount === totalCount) {
|
||||
backToFileList()
|
||||
}
|
||||
}
|
||||
@@ -1355,8 +1363,10 @@ const canSubmitReview = computed(() => {
|
||||
if (totalIssues === 0) return true
|
||||
return allIssuesResolved.value
|
||||
})
|
||||
const hasSubmittedPassReview = ref(false)
|
||||
|
||||
async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
hasSubmittedPassReview.value = verdict === 'safe'
|
||||
const editorContent = threadViewRef.value?.getReplyContent() || ''
|
||||
|
||||
let message: string | undefined
|
||||
@@ -1523,13 +1533,39 @@ function copyId() {
|
||||
|
||||
<div class="h-px w-full bg-surface-5"></div>
|
||||
|
||||
<NavTabs
|
||||
mode="local"
|
||||
:links="navTabsLinks"
|
||||
:active-index="activeTabIndex"
|
||||
class="bg-surface-3! shadow-none!"
|
||||
@tab-click="handleTabClick"
|
||||
/>
|
||||
<div class="flex flex-row justify-between">
|
||||
<NavTabs
|
||||
mode="local"
|
||||
:links="navTabsLinks"
|
||||
:active-index="activeTabIndex"
|
||||
class="bg-surface-3! shadow-none!"
|
||||
@tab-click="handleTabClick"
|
||||
/>
|
||||
|
||||
<div v-if="selectedFile" class="flex flex-row items-end gap-2">
|
||||
<ButtonLink
|
||||
v-tooltip="`Download`"
|
||||
type="outlined"
|
||||
target="_blank"
|
||||
:href="selectedFile.download_url"
|
||||
:download="selectedFile.file_name"
|
||||
class="!bg-surface-2"
|
||||
aria-label="Download"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
v-tooltip="`Open in Slicer`"
|
||||
type="outlined"
|
||||
target="_blank"
|
||||
:href="`https://slicer.run/?url=${encodeURIComponent(selectedFile.download_url)}`"
|
||||
class="!bg-surface-2"
|
||||
aria-label="Open in Slicer"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" /> Open
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-surface-3 bg-surface-2">
|
||||
@@ -1552,10 +1588,16 @@ function copyId() {
|
||||
>
|
||||
<template #additionalActions>
|
||||
<Button
|
||||
v-tooltip="!canSubmitReview ? 'There are still pending flags!' : undefined"
|
||||
v-tooltip="
|
||||
!canSubmitReview
|
||||
? 'There are still pending flags!'
|
||||
: hasSubmittedPassReview
|
||||
? 'Project already passed!'
|
||||
: undefined
|
||||
"
|
||||
type="colored"
|
||||
color="brand"
|
||||
:disabled="!canSubmitReview"
|
||||
:disabled="!canSubmitReview || hasSubmittedPassReview"
|
||||
@click="handleSubmitReview('safe')"
|
||||
>
|
||||
<ShieldCheckIcon /> Pass
|
||||
@@ -1683,7 +1725,16 @@ function copyId() {
|
||||
:download="file.file_name"
|
||||
tabindex="0"
|
||||
>
|
||||
<DownloadIcon /> Download
|
||||
<DownloadIcon />
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
v-tooltip="`Open in Slicer`"
|
||||
type="outlined"
|
||||
target="_blank"
|
||||
:href="`https://slicer.run/?url=${encodeURIComponent(file.download_url)}`"
|
||||
aria-label="Open in Slicer"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" /> Open
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2063,7 +2114,7 @@ function copyId() {
|
||||
v-tooltip="`Copy code`"
|
||||
type="quiet"
|
||||
:label="`Copy code`"
|
||||
class="absolute right-2 top-2 border-[1px]"
|
||||
class="!absolute right-2 top-2 border-[1px]"
|
||||
@click="copyToClipboard(getClassDecompiledSource(classItem)!, classItem.key)"
|
||||
>
|
||||
<CopyIcon v-if="!showCopyFeedback.get(classItem.key)" />
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Button, IconButton } from '@modrinth/ui'
|
||||
import {
|
||||
Combobox,
|
||||
type ComboboxOption,
|
||||
commonMessages,
|
||||
ConfirmModal,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
@@ -61,10 +62,6 @@ const messages = defineMessages({
|
||||
id: 'modpack-scan-modal.scanning',
|
||||
defaultMessage: 'Scanning...',
|
||||
},
|
||||
success: {
|
||||
id: 'modpack-scan-modal.success',
|
||||
defaultMessage: 'Success',
|
||||
},
|
||||
failed: {
|
||||
id: 'modpack-scan-modal.failed',
|
||||
defaultMessage: 'Failed',
|
||||
@@ -102,10 +99,6 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'This will permanently delete all attribution groups for this project and all files inside them. This action cannot be undone.',
|
||||
},
|
||||
deleteAllGroupsConfirmationProceed: {
|
||||
id: 'modpack-scan-modal.delete-all-groups.proceed',
|
||||
defaultMessage: 'Clear',
|
||||
},
|
||||
deleteAllGroupsSuccess: {
|
||||
id: 'modpack-scan-modal.delete-all-groups.success',
|
||||
defaultMessage: 'All groups cleared successfully.',
|
||||
@@ -344,7 +337,7 @@ async function clearAllGroups() {
|
||||
if (!failed) {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: formatMessage(messages.success),
|
||||
title: formatMessage(commonMessages.successLabel),
|
||||
text: formatMessage(messages.deleteAllGroupsSuccess),
|
||||
})
|
||||
}
|
||||
@@ -369,7 +362,7 @@ defineExpose({ show, hide })
|
||||
ref="clearModalRef"
|
||||
:title="formatMessage(messages.deleteAllGroupsConfirmationTitle)"
|
||||
:description="formatMessage(messages.deleteAllGroupsConfirmationDescription)"
|
||||
:proceed-label="formatMessage(messages.deleteAllGroupsConfirmationProceed)"
|
||||
:proceed-label="formatMessage(commonMessages.clearButton)"
|
||||
@proceed="clearAllGroups"
|
||||
/>
|
||||
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
<div>
|
||||
<span class="flex flex-row items-center gap-2 text-sm text-secondary">
|
||||
<GlobeIcon
|
||||
v-if="props.global"
|
||||
v-if="props.scope === 'project'"
|
||||
v-tooltip="'Can be used without the checklist open if setting enabled.'"
|
||||
/>
|
||||
<ShieldCheckIcon
|
||||
v-if="props.scope === 'tech-review'"
|
||||
v-tooltip="'Used within the tech review pages'"
|
||||
/>
|
||||
{{ props.title }}
|
||||
<IconButton
|
||||
type="quiet"
|
||||
@@ -45,14 +49,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GlobeIcon, RotateCounterClockwiseIcon } from '@modrinth/assets'
|
||||
import { GlobeIcon, RotateCounterClockwiseIcon, ShieldCheckIcon } from '@modrinth/assets'
|
||||
import { type KeybindDefinition, toKeybindDefinition } from '@modrinth/moderation'
|
||||
import { IconButton } from '@modrinth/ui'
|
||||
import { onUnmounted } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
global: boolean
|
||||
scope: string
|
||||
definitions: KeybindDefinition[]
|
||||
default: KeybindDefinition[]
|
||||
onChange: (definitions: KeybindDefinition[]) => void
|
||||
|
||||
@@ -12,7 +12,7 @@ const keybinds = useModerationKeybinds()
|
||||
v-for="[id, keybind] in keybinds"
|
||||
:key="id"
|
||||
:title="keybind.description"
|
||||
:global="keybind.scope === 'project'"
|
||||
:scope="keybind.scope"
|
||||
:definitions="keybind.keybind"
|
||||
:default="keybind.defaultKeybind"
|
||||
:on-change="
|
||||
|
||||
@@ -42,8 +42,7 @@ const plans: Record<
|
||||
Plan,
|
||||
{
|
||||
buttonColor: 'blue' | 'green' | 'purple'
|
||||
accentText: string
|
||||
accentBg: string
|
||||
buttonClasses: string
|
||||
name: MessageDescriptor
|
||||
description: MessageDescriptor
|
||||
mostPopular: boolean
|
||||
@@ -51,8 +50,7 @@ const plans: Record<
|
||||
> = {
|
||||
small: {
|
||||
buttonColor: 'blue',
|
||||
accentText: 'text-blue',
|
||||
accentBg: 'bg-bg-blue',
|
||||
buttonClasses: '!bg-highlight-blue !text-blue',
|
||||
name: commonMessages.planSmallLabel,
|
||||
description: defineMessage({
|
||||
id: 'servers.plan.small.description',
|
||||
@@ -62,8 +60,7 @@ const plans: Record<
|
||||
},
|
||||
medium: {
|
||||
buttonColor: 'green',
|
||||
accentText: 'text-green',
|
||||
accentBg: 'bg-bg-green',
|
||||
buttonClasses: '',
|
||||
name: commonMessages.planMediumLabel,
|
||||
description: defineMessage({
|
||||
id: 'servers.plan.medium.description',
|
||||
@@ -73,8 +70,7 @@ const plans: Record<
|
||||
},
|
||||
large: {
|
||||
buttonColor: 'purple',
|
||||
accentText: 'text-purple',
|
||||
accentBg: 'bg-bg-purple',
|
||||
buttonClasses: '!bg-highlight-purple !text-purple',
|
||||
name: commonMessages.planLargeLabel,
|
||||
description: defineMessage({
|
||||
id: 'servers.plan.large.description',
|
||||
@@ -150,16 +146,20 @@ const billingMonths = computed(() => {
|
||||
v-if="outOfStock"
|
||||
disabled
|
||||
size="xl"
|
||||
:type="plans[plan].mostPopular ? 'colored' : 'base'"
|
||||
:color="plans[plan].mostPopular ? plans[plan].buttonColor : undefined"
|
||||
:type="plans[plan].mostPopular ? 'colored' : 'quiet'"
|
||||
:color="plans[plan].buttonColor"
|
||||
:interaction="plans[plan].mostPopular ? undefined : 'none'"
|
||||
:class="plans[plan].buttonClasses"
|
||||
>
|
||||
{{ formatMessage(messages.outOfStock) }}
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
size="xl"
|
||||
:type="plans[plan].mostPopular ? 'colored' : 'base'"
|
||||
:color="plans[plan].mostPopular ? plans[plan].buttonColor : undefined"
|
||||
:type="plans[plan].mostPopular ? 'colored' : 'quiet'"
|
||||
:color="plans[plan].buttonColor"
|
||||
:interaction="plans[plan].mostPopular ? undefined : 'none'"
|
||||
:class="plans[plan].buttonClasses"
|
||||
@click="() => emit('select')"
|
||||
>
|
||||
{{ formatMessage(messages.selectPlanButton) }}
|
||||
|
||||
@@ -138,22 +138,6 @@
|
||||
<BracesIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.datapack) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
type="quiet"
|
||||
to="/discover/modpacks"
|
||||
:class="
|
||||
route.name === 'discover-modpacks' || route.path.startsWith('/modpack/')
|
||||
? (route.name === 'discover-modpacks'
|
||||
? 'main-nav-primary'
|
||||
: 'main-nav-secondary') === 'main-nav-primary'
|
||||
? '!bg-[var(--color-button-bg-selected)] !text-[var(--color-button-text-selected)] [&>svg]:!text-[var(--color-button-text-selected)]'
|
||||
: '!bg-[var(--color-button-bg)] !text-contrast'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<PackageOpenIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.modpack) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
type="quiet"
|
||||
to="/discover/shaders"
|
||||
@@ -170,6 +154,22 @@
|
||||
<GlassesIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.shader) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
type="quiet"
|
||||
to="/discover/modpacks"
|
||||
:class="
|
||||
route.name === 'discover-modpacks' || route.path.startsWith('/modpack/')
|
||||
? (route.name === 'discover-modpacks'
|
||||
? 'main-nav-primary'
|
||||
: 'main-nav-secondary') === 'main-nav-primary'
|
||||
? '!bg-[var(--color-button-bg-selected)] !text-[var(--color-button-text-selected)] [&>svg]:!text-[var(--color-button-text-selected)]'
|
||||
: '!bg-[var(--color-button-bg)] !text-contrast'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<PackageOpenIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.modpack) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
type="quiet"
|
||||
to="/discover/plugins"
|
||||
@@ -1215,8 +1215,8 @@ const navRoutes = computed(() => [
|
||||
href: '/discover/mods',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('plugin', true)),
|
||||
href: '/discover/plugins',
|
||||
label: formatMessage(getProjectTypeMessage('resourcepack', true)),
|
||||
href: '/discover/resourcepacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('datapack', true)),
|
||||
@@ -1226,14 +1226,14 @@ const navRoutes = computed(() => [
|
||||
label: formatMessage(getProjectTypeMessage('shader', true)),
|
||||
href: '/discover/shaders',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('resourcepack', true)),
|
||||
href: '/discover/resourcepacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('modpack', true)),
|
||||
href: '/discover/modpacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('plugin', true)),
|
||||
href: '/discover/plugins',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('server', true)),
|
||||
href: '/discover/servers',
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Kürzlich aktualisiert"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Schließen"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "{formType} herunterladen"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Fehler beim Löschen aller Gruppen: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Löschen"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Alle Gruppen wurden gelöscht."
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Wird gescannt..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Erfolg"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Modpack-Scan ({scanned}/{total} Dateien)"
|
||||
},
|
||||
@@ -3938,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Hervorgehobene Tags"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Du kannst bis zu 3 deiner relevantesten Tags als hervorheben. Andere Tags werden möglicherweise hervorgehoben, falls du nicht alle 3 auswählst."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Wähle alle Funktionen aus, die dein {type} nutzt."
|
||||
},
|
||||
@@ -3956,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Wähle mindestens eine Kategorie aus, um eine Kategorie hervorzuheben."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Genaue Tags sind wichtig, damit Nutzer dein {type} leichter finden können. Achte darauf, alle zutreffenden Tags auszuwählen."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Kürzlich aktualisiert"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Schließen"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Lade {formType} herunter"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Fehler beim Löschen aller Gruppen: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Löschen"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Alle Gruppen wurden gelöscht."
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Wird gescannt..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Erfolg"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Modpack-Scan ({scanned}/{total} Dateien)"
|
||||
},
|
||||
@@ -3938,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Hervorgehobene Tags"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Du kannst bis zu 3 deiner relevantesten Tags als hervorheben. Andere Tags werden möglicherweise hervorgehoben, falls du nicht alle 3 auswählst."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Wähle alle Funktionen aus, die dein {type} nutzt."
|
||||
},
|
||||
@@ -3956,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Wähle mindestens eine Kategorie aus, um eine Kategorie hervorzuheben."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Genaue Tags sind wichtig, damit Nutzer dein {type} leichter finden können. Achte darauf, alle zutreffenden Tags auszuwählen."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Recently Updated"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Close"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Download {formType}"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Failed to clear all groups: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Clear"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "All groups cleared successfully."
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Scanning..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Success"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Modpack Scan ({scanned}/{total} Files)"
|
||||
},
|
||||
@@ -4169,12 +4160,27 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Featured tags"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "You can feature up to 3 of your most relevant tags. Other tags may be promoted to featured if you do not select all 3."
|
||||
"project.settings.tags.featured-tags-required": {
|
||||
"message": "You must have at least one featured tag."
|
||||
},
|
||||
"project.settings.tags.featured-tags-select-description": {
|
||||
"message": "Select your most relevant tags. These are displayed before the rest of your tags."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Select all of the features that your {type} makes use of."
|
||||
},
|
||||
"project.settings.tags.group-title.categories": {
|
||||
"message": "{showType, select, yes {{types} categories} other {Categories}}"
|
||||
},
|
||||
"project.settings.tags.group-title.features": {
|
||||
"message": "{showType, select, yes {{types} features} other {Features}}"
|
||||
},
|
||||
"project.settings.tags.group-title.performance-impact": {
|
||||
"message": "{showType, select, yes {{types} performance impact} other {Performance impact}}"
|
||||
},
|
||||
"project.settings.tags.group-title.resolutions": {
|
||||
"message": "{showType, select, yes {{types} resolutions} other {Resolutions}}"
|
||||
},
|
||||
"project.settings.tags.multiple-resolution-tags-warning": {
|
||||
"message": "You've selected {count} resolution tags ({tags}). Resource packs should typically only have one resolution tag."
|
||||
},
|
||||
@@ -4187,9 +4193,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Select at least one category in order to feature a category."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Accurate tagging is important to help people find your {type}. Make sure to select all tags that apply."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Actualizado recientemente"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Cerrar"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Descargar {formType}"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Error al borrar todos los grupos: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Borrar"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Todos los grupos fueron borrados exitosamente."
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Escaneando..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Éxito"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Análisis del modpack ({scanned}/{total} archivos)"
|
||||
},
|
||||
@@ -3938,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Etiquetas destacadas"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Puedes destacar hasta 3 de tus etiquetas más relevantes. Otras etiquetas pueden ser promovidas a destacadas si no seleccionas las 3 completas."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Selecciona todas las características que tu {type} usa."
|
||||
},
|
||||
@@ -3956,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Selecciona al menos una categoría para destacar una categoría."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Seleccionar etiquetas relevantes es importante para ayudar a la gente a encontrar tu {type}. Asegúrate de seleccionar todas las etiquetas que apliquen."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Etiquetas"
|
||||
},
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Mis à jour récemment"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Fermer"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Télécharger {formType}"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Le nettoyage des groupes a échoué : {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Effacer"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Tous les groupes ont été nettoyés avec succès."
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Scan en cours..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Succès"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Scan du modpack ({scanned}/{total} fichiers)"
|
||||
},
|
||||
@@ -3938,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Tags mis en avant"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Vous pouvez mettre en avant jusqu'à 3 de vos tags les plus pertinents. D'autres tags peuvent être mis en avant si vous ne sélectionnez pas les 3."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Sélectionnez toutes les fonctionnalités que votre {type} utilise."
|
||||
},
|
||||
@@ -3956,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Sélectionnez au moins une catégorie afin de pouvoir mettre en avant une catégorie."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Un étiquetage correct est important pour aider les gens à trouver votre {type}. Faites en sorte de sélectionner tous les tags qui s'appliquent."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -1415,9 +1415,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Nemrég frissített"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Bezárás"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Letöltés {formType}"
|
||||
},
|
||||
@@ -3644,9 +3641,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Kiemelt címkék"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Legfeljebb 3 legrelevánsabb címkét jelölhetsz ki kiemeltként. Ha nem választod ki mind a 3-at, akkor a többi címke is kiemeltként jelenhet meg."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Jelöld ki az összes olyan funkciót, amelyet a {type} használ."
|
||||
},
|
||||
@@ -3662,9 +3656,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Válassz ki legalább egy kategóriát ahhoz, hogy kiemelj egy kategóriát."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "A pontos címkézés fontos ahhoz, hogy a {type} könnyebben me. Feltétlenül válaszd ki az összes releváns címkét."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Címkék"
|
||||
},
|
||||
|
||||
@@ -1475,9 +1475,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Ultima modifica"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Chiudi"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Scarica {formType}"
|
||||
},
|
||||
@@ -2975,9 +2972,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Errore nell'eliminazione dei gruppi: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Procedi"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Tutti i gruppi eliminati con successo."
|
||||
},
|
||||
@@ -3017,9 +3011,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Scansione..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Successo"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Scansione del pacchetto ({scanned}/{total} file)"
|
||||
},
|
||||
@@ -3926,9 +3917,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Tag in evidenza"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Puoi mettere in evidenza fino a 3 tag. Altri tag potrebbero aggiungersi se non bastassero."
|
||||
},
|
||||
"project.settings.tags.multiple-resolution-tags-warning": {
|
||||
"message": "Hai selezionato {count} tag di risoluzione ({tags}). I pacchetti di risorse dovrebbero averne solo uno."
|
||||
},
|
||||
|
||||
@@ -1109,9 +1109,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "更新順(新しい順)"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "閉じる"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "{formType} をダウンロード"
|
||||
},
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "최근 업데이트"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "닫기"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "{formType} 다운로드"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "모든 그룹 삭제 실패: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "삭제"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "모든 그룹 삭제 완료."
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "스캔중..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "성공"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "모드팩 스캔 ({scanned}/{total} 파일)"
|
||||
},
|
||||
@@ -3938,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "추천 태그"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "가장 적합한 태그를 최대 3개까지 지정할 수 있습니다. 3개를 모두 선택하지 않으면 다른 태그가 추천 태그로 표시될 수 있습니다."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": " {type}에서 사용하는 모든 기능을 선택하세요."
|
||||
},
|
||||
@@ -3956,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "추천 카테고리를 지정하려면 적어도 하나의 카테고리를 선택해야 합니다."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "정확한 태그 선택은 {type}을 쉽게 찾을 수 있도록 돕습니다. 관련된 모든 태그를 선택해 주세요."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "태그"
|
||||
},
|
||||
|
||||
@@ -1112,9 +1112,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Dikemas Kini Baru-baru Ini"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Tutup"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Muat turun {formType}"
|
||||
},
|
||||
@@ -2423,9 +2420,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Semakan teknikal"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Kosongkan"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Gagal"
|
||||
},
|
||||
|
||||
@@ -1478,9 +1478,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Recentelijk geüpdate"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Sluiten"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Download {formType}"
|
||||
},
|
||||
@@ -2981,9 +2978,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Niet gelukt om alle groepen te wissen: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Wissen"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Alle groepen zijn succesvol gewist."
|
||||
},
|
||||
@@ -3023,9 +3017,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Scannen..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Gelukt"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Modpack-scan ({scanned}/{total} bestanden)"
|
||||
},
|
||||
@@ -3935,9 +3926,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Uitgelichte tags"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Je kunt maximaal 3 van je meest relevante tags als uitgelichte tags selecteren. Als je niet alle 3 selecteert, kunnen andere tags als uitgelichte tags worden weergegeven."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Selecteer alle functies waarvan je {type} gebruikmaakt."
|
||||
},
|
||||
@@ -3953,9 +3941,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Selecteer minstens één categorie om een categorie uit te lichten."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Nauwkeurige tags zijn belangrijk om mensen te helpen je {type} te vinden. Zorg ervoor dat je alle relevante tags selecteert."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -1478,9 +1478,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Ostatnio zaktualizowane"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Zamknij"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Pobierz {formType}"
|
||||
},
|
||||
@@ -2975,9 +2972,6 @@
|
||||
"modpack-scan-modal.delete-all-groups-confirmation.description": {
|
||||
"message": "Trwale usunie to wszystkie grupy atrybucji i wszystkie zawarte w nich pliki. Tej akcji nie można cofnąć."
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Wyczyść"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Błąd"
|
||||
},
|
||||
@@ -3014,9 +3008,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Skanowanie..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Sukces"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Skanowanie paczki modów ({scanned}/{total} plików)"
|
||||
},
|
||||
@@ -3884,9 +3875,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Wyróżnione tagi"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Możesz wyróżnić 3 z najlepiej pasujących do Twojego projektu tagów. Inne tagi mogą zostać pokazane jako wyróżnione, jeżeli nie wybierzesz wszystkich trzech."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Wszystkie funkcjonalności, których używa {type}"
|
||||
},
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Atualizado recentemente"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Fechar"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Baixar {formType}"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Falhou ao excluir todos os grupos: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Excluir"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Todos os grupos excluídos com sucesso."
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Verificando..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Sucesso"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Verificação do pacote de mods ({scanned}/{total} Arquivos)"
|
||||
},
|
||||
@@ -3938,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Etiquetas em destaque"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Você pode destacar até 3 das suas etiquetas mais relevantes. Outras etiquetas poderão ser promovidas a destaque caso você não selecione todas as 3."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Selecione todas as funcionalidades que o seu {type} utiliza."
|
||||
},
|
||||
@@ -3956,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Selecione pelo menos uma categoria para ela ser destacada."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Etiquetar corretamente é importante para ajudar as pessoas a encontrarem seu {type}. Certifique-se de selecionar todas as etiquetas aplicáveis."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Etiquetas"
|
||||
},
|
||||
|
||||
@@ -1475,9 +1475,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Дата обновления"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Закрыть"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Скачать {formType}"
|
||||
},
|
||||
@@ -2978,9 +2975,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Не удалось удалить все группы: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Удалить"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Все группы успешно удалены."
|
||||
},
|
||||
@@ -3020,9 +3014,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Сканирование..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Готово"
|
||||
},
|
||||
"muralpay.account-type.checking": {
|
||||
"message": "Расчётный"
|
||||
},
|
||||
@@ -3929,9 +3920,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Ключевые теги"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Можно выбрать до 3 ключевых тегов. Если не выбрать все 3, то другие теги могут стать ключевыми."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Выберите все особенности, которые использует ваш {type}"
|
||||
},
|
||||
@@ -3944,9 +3932,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Выберите хотя бы одну категорию, чтобы отобразить ее."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Аккуратная маркировка важна, чтобы помочь людям найти ваш {type}"
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Теги"
|
||||
},
|
||||
|
||||
@@ -1313,9 +1313,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Nyligen uppdaterade"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Stäng"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Ladda ner {formType}"
|
||||
},
|
||||
@@ -2645,9 +2642,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Misslyckades att tömma alla grupper: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Töm"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Misslyckades"
|
||||
},
|
||||
|
||||
@@ -1358,9 +1358,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Son Güncellenen"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Kapat"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "{formType} İndirin"
|
||||
},
|
||||
@@ -2774,9 +2771,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Taranıyor..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Başarılı"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Mod paketi taraması ({scanned}/{total} Dosyalar)"
|
||||
},
|
||||
|
||||
@@ -1478,9 +1478,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Нещодавно оновлені"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Закрити"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Завантажити {formType}"
|
||||
},
|
||||
@@ -2981,9 +2978,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Не вдалося очистити всі групи: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Очистити"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "Усі групи успішно очищено."
|
||||
},
|
||||
@@ -3023,9 +3017,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Скануємо…"
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Успіх"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Сканування збірки ({scanned}/{total} файлів)"
|
||||
},
|
||||
@@ -3935,9 +3926,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Основні теґи"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Ви можете обрати основними до 3 найбільш описових теґів. Інші теґи можуть стати основними, якщо не оберете всі 3."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Оберіть увесь функціонал, який присутній у {type}."
|
||||
},
|
||||
@@ -3953,9 +3941,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Виберіть хоча б одну категорію, щоб рекомендувати її."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Указання правильних теґів необхідне, щоб люди могли легше знайти ваш {type}. Переконайтеся, що ви обрали підхожі теґи."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Теґи"
|
||||
},
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "最近更新"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "关闭"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "下载{formType}"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "未能清除所有组:{error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "清除"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "所有组均已成功清除。"
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "扫描中……"
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "成功"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "模组包扫描 ({scanned}/{total} 个文件)"
|
||||
},
|
||||
@@ -3938,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "精选标签"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "你可以设置最多 3 个精选标签,标示出最相关的特点。如果没有选完 3 个标签,其他标签也可能提升为精选标签。"
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "选择你的{type}作用于哪类特性。"
|
||||
},
|
||||
@@ -3956,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "请先至少选择一个分类,再设置精选分类。"
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "准确选好标签,能极大帮助别人找到你的{type}。请确保选择所有合适的标签。"
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "标签"
|
||||
},
|
||||
|
||||
@@ -1481,9 +1481,6 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "最近更新"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "關閉"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "下載 {formType}"
|
||||
},
|
||||
@@ -2984,9 +2981,6 @@
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "無法清除所有群組:{error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "清除"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "已成功清除所有群組。"
|
||||
},
|
||||
@@ -3026,9 +3020,6 @@
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "正在掃描..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "成功"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "模組包掃描({scanned}/{total} 個檔案)"
|
||||
},
|
||||
@@ -3938,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "精選標籤"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "你可以選擇最多 3 個最相關的標籤作為精選標籤。如果沒有選完 3 個標籤,系統可能會自動將其他標籤選為精選標籤。"
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "請選擇你的{type}包含的功能。"
|
||||
},
|
||||
@@ -3956,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "請選擇至少一個類別再選擇精選標籤。"
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "精確的標籤對於協助使用者找到你的{type}非常重要。請務必選擇所有合適的標籤。"
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "標籤"
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<ProjectBackgroundGradient :project="project" />
|
||||
</Teleport>
|
||||
<template v-if="isSettings">
|
||||
<div v-if="canAccessSettings" class="normal-page no-sidebar">
|
||||
<div v-if="canAccessSettings" class="normal-page no-sidebar" :class="`align-${marginTarget}`">
|
||||
<div class="normal-page__header mb-6">
|
||||
<PageHeader :title="project.title" :row-class="'items-center'">
|
||||
<template #leading>
|
||||
@@ -115,17 +115,20 @@
|
||||
/>
|
||||
<div
|
||||
class="new-page sidebar"
|
||||
:class="{
|
||||
'alt-layout': cosmetics.leftContentLayout,
|
||||
'checklist-open':
|
||||
showModerationChecklist &&
|
||||
!collapsedModerationChecklist &&
|
||||
!flags.alwaysShowChecklistAsPopup,
|
||||
'checklist-collapsed':
|
||||
showModerationChecklist &&
|
||||
collapsedModerationChecklist &&
|
||||
!flags.alwaysShowChecklistAsPopup,
|
||||
}"
|
||||
:class="[
|
||||
{
|
||||
'alt-layout': cosmetics.leftContentLayout,
|
||||
'checklist-open':
|
||||
showModerationChecklist &&
|
||||
!collapsedModerationChecklist &&
|
||||
!flags.alwaysShowChecklistAsPopup,
|
||||
'checklist-collapsed':
|
||||
showModerationChecklist &&
|
||||
collapsedModerationChecklist &&
|
||||
!flags.alwaysShowChecklistAsPopup,
|
||||
},
|
||||
`align-${marginTarget}`,
|
||||
]"
|
||||
>
|
||||
<div
|
||||
class="normal-page__header relative mb-4"
|
||||
@@ -453,7 +456,8 @@
|
||||
|
||||
<div class="normal-page__sidebar">
|
||||
<ProjectSidebarServerInfo
|
||||
v-if="isServerProject && serverDataLoaded"
|
||||
v-if="isServerProject"
|
||||
:loading="!serverDataLoaded"
|
||||
:project-v3="projectV3"
|
||||
:tags="tags"
|
||||
:required-content="serverRequiredContent"
|
||||
@@ -483,6 +487,7 @@
|
||||
<ProjectSidebarCreators
|
||||
:organization="organization"
|
||||
:members="members"
|
||||
:loading="creatorsLoading"
|
||||
:org-link="(slug) => `/organization/${slug}`"
|
||||
:user-link="(username) => `/user/${username}`"
|
||||
class="card flex-card"
|
||||
@@ -490,6 +495,7 @@
|
||||
<ProjectSidebarDetails
|
||||
:project="project"
|
||||
:link-target="$external()"
|
||||
:hide-license="isServerProject"
|
||||
:show-followers="isServerProject"
|
||||
class="card flex-card"
|
||||
/>
|
||||
@@ -535,7 +541,7 @@ import {
|
||||
SettingsIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { moderationSettings } from '@modrinth/moderation'
|
||||
import { getMarginTarget, moderationSettings } from '@modrinth/moderation'
|
||||
import {
|
||||
Admonition,
|
||||
ArchivedProjectBanner,
|
||||
@@ -612,6 +618,7 @@ const config = useRuntimeConfig()
|
||||
const moderationQueue = useModerationQueue()
|
||||
const keybinds = useModerationKeybinds()
|
||||
const modSettings = useModerationSettings()
|
||||
const marginTarget = computed(() => getMarginTarget(modSettings.value))
|
||||
const notifications = injectNotificationManager()
|
||||
const { addNotification } = notifications
|
||||
|
||||
@@ -1091,7 +1098,11 @@ watch(serverModpackVersionId, (versionId) => {
|
||||
})
|
||||
|
||||
// Members
|
||||
const { data: allMembersRaw, error: _membersError } = useQuery({
|
||||
const {
|
||||
data: allMembersRaw,
|
||||
error: _membersError,
|
||||
isPending: membersPending,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => ['project', projectId.value, 'members']),
|
||||
queryFn: () => client.labrinth.projects_v3.getMembers(projectId.value),
|
||||
staleTime: STALE_TIME,
|
||||
@@ -1142,7 +1153,7 @@ const {
|
||||
|
||||
// Organization
|
||||
// Only fetch organization if project belongs to one
|
||||
const { data: organizationRaw } = useQuery({
|
||||
const { data: organizationRaw, isPending: organizationPending } = useQuery({
|
||||
queryKey: computed(() => ['project', projectId.value, 'organization']),
|
||||
queryFn: () => client.labrinth.projects_v3.getOrganization(projectId.value),
|
||||
staleTime: STALE_TIME,
|
||||
@@ -1166,6 +1177,13 @@ const archivedDisclosure = computed(() =>
|
||||
)
|
||||
const isArchived = computed(() => !!archivedDisclosure.value)
|
||||
|
||||
const creatorsLoading = computed(
|
||||
() =>
|
||||
!projectRaw.value ||
|
||||
membersPending.value ||
|
||||
(!!projectRaw.value.organization && organizationPending.value),
|
||||
)
|
||||
|
||||
const { data: thread } = useQuery({
|
||||
queryKey: computed(() => ['thread', projectRaw.value?.thread_id]),
|
||||
queryFn: () => client.labrinth.threads_v3.getThread(projectRaw.value.thread_id),
|
||||
@@ -2318,7 +2336,7 @@ provideProjectPageContext({
|
||||
|
||||
.servers-popup {
|
||||
box-shadow:
|
||||
0 0 12px 1px rgba(0, 175, 92, 0.6),
|
||||
0 0 12px 1px color-mix(in srgb, var(--color-brand) 60%, transparent),
|
||||
var(--shadow-floating);
|
||||
|
||||
&::before {
|
||||
|
||||
@@ -1,174 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<ConfirmLeaveModal ref="confirmLeaveModal" />
|
||||
<section class="universal-card">
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">{{ formatMessage(messages.tagsTitle) }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="tooManyTagsWarning && !allTagsSelectedWarning"
|
||||
class="my-2 flex items-center gap-1.5 text-orange"
|
||||
>
|
||||
<TriangleAlertIcon class="my-auto" />
|
||||
{{ tooManyTagsWarning }}
|
||||
</div>
|
||||
|
||||
<div v-if="multipleResolutionTagsWarning" class="my-2 flex items-center gap-1.5 text-orange">
|
||||
<TriangleAlertIcon class="my-auto" />
|
||||
{{ multipleResolutionTagsWarning }}
|
||||
</div>
|
||||
|
||||
<div v-if="allTagsSelectedWarning" class="my-2 flex items-center gap-1.5 text-red">
|
||||
<TriangleAlertIcon class="my-auto" />
|
||||
<span>{{ allTagsSelectedWarning }}</span>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{
|
||||
formatMessage(messages.taggingImportanceDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="project.versions.length === 0 && projectV3?.minecraft_server == null"
|
||||
class="known-errors"
|
||||
>
|
||||
{{ formatMessage(messages.uploadVersionFirst) }}
|
||||
</p>
|
||||
<template v-else>
|
||||
<template v-for="header in Object.keys(categoryLists)" :key="`categories-${header}`">
|
||||
<div class="label mb-3">
|
||||
<h4>
|
||||
<span class="label__title">{{ formatCategoryHeader(formatMessage, header) }}</span>
|
||||
</h4>
|
||||
<span class="label__description">
|
||||
<template v-if="header === 'categories'">
|
||||
{{
|
||||
formatMessage(messages.categoriesDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'features'">
|
||||
{{
|
||||
formatMessage(messages.featuresDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'resolutions'">
|
||||
{{
|
||||
formatMessage(messages.resolutionsDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'performance impact'">
|
||||
{{
|
||||
formatMessage(messages.performanceImpactDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="category-list input-div">
|
||||
<Checkbox
|
||||
v-for="category in categoryLists[header]"
|
||||
:key="`category-${header}-${category.name}`"
|
||||
:model-value="current.selectedTags.includes(category)"
|
||||
:description="formatCategory(formatMessage, category.name)"
|
||||
class="category-selector"
|
||||
@update:model-value="toggleCategory(category)"
|
||||
>
|
||||
<div class="category-selector__label">
|
||||
<component
|
||||
:is="getTagIcon(category.name)"
|
||||
v-if="header !== 'resolutions' && getTagIcon(category.name)"
|
||||
aria-hidden="true"
|
||||
class="icon"
|
||||
/>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="category.name" enforce-type="category" />
|
||||
</span>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</template>
|
||||
<div class="label">
|
||||
<h4>
|
||||
<span class="label__title"
|
||||
><StarIcon /> {{ formatMessage(messages.featuredTags) }}</span
|
||||
>
|
||||
</h4>
|
||||
<span class="label__description">{{
|
||||
formatMessage(messages.featuredTagsDescription)
|
||||
}}</span>
|
||||
</div>
|
||||
<p v-if="current.selectedTags.length < 1">
|
||||
{{ formatMessage(messages.selectAtLeastOneCategory) }}
|
||||
</p>
|
||||
<div class="category-list input-div">
|
||||
<Checkbox
|
||||
v-for="category in current.selectedTags"
|
||||
:key="`featured-category-${category.name}`"
|
||||
class="category-selector"
|
||||
:model-value="current.featuredTags.includes(category)"
|
||||
:description="formatCategory(formatMessage, category.name)"
|
||||
:disabled="current.featuredTags.length >= 3 && !current.featuredTags.includes(category)"
|
||||
@update:model-value="toggleFeaturedCategory(category)"
|
||||
>
|
||||
<div class="category-selector__label">
|
||||
<component
|
||||
:is="getTagIcon(category.name)"
|
||||
v-if="category.header !== 'resolutions' && getTagIcon(category.name)"
|
||||
aria-hidden="true"
|
||||
class="icon"
|
||||
/>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="category.name" enforce-type="category" />
|
||||
</span>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
<UnsavedChangesPopup
|
||||
:original="saved"
|
||||
:modified="current"
|
||||
:saving="saving"
|
||||
@reset="reset"
|
||||
@save="save"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
getCategoryIcon,
|
||||
SERVER_CATEGORY_ICON_MAP,
|
||||
StarIcon,
|
||||
TriangleAlertIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Admonition,
|
||||
Checkbox,
|
||||
ConfirmLeaveModal,
|
||||
defineMessages,
|
||||
formatCategory,
|
||||
formatCategoryHeader,
|
||||
formatProjectTypeSentence,
|
||||
FormattedTag,
|
||||
injectProjectPageContext,
|
||||
type MessageDescriptor,
|
||||
sortProjectTypes,
|
||||
TagItem,
|
||||
UnsavedChangesPopup,
|
||||
usePageLeaveSafety,
|
||||
useSavable,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatProjectType, sortedCategories } from '@modrinth/utils'
|
||||
import { capitalizeString, sortedCategories } from '@modrinth/utils'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Category {
|
||||
@@ -178,19 +27,30 @@ interface Category {
|
||||
project_type: string
|
||||
}
|
||||
|
||||
interface CategoryGroup {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
categories: Category[]
|
||||
}
|
||||
|
||||
const MAX_FEATURED_TAGS = 3
|
||||
|
||||
const RESOLUTION_TAGS = ['8x-', '16x', '32x', '48x', '64x', '128x', '256x', '512x+']
|
||||
|
||||
const SHARED_CATEGORY_PROJECT_TYPES: Record<string, string> = {
|
||||
plugin: 'mod',
|
||||
datapack: 'mod',
|
||||
}
|
||||
|
||||
const tags = useGeneratedState()
|
||||
const { formatMessage, locale } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
tagsTitle: {
|
||||
title: {
|
||||
id: 'project.settings.tags.title',
|
||||
defaultMessage: 'Tags',
|
||||
},
|
||||
taggingImportanceDescription: {
|
||||
id: 'project.settings.tags.tagging-importance-description',
|
||||
defaultMessage:
|
||||
'Accurate tagging is important to help people find your {type}. Make sure to select all tags that apply.',
|
||||
},
|
||||
uploadVersionFirst: {
|
||||
id: 'project.settings.tags.upload-version-first',
|
||||
defaultMessage: 'Please upload a version first in order to select tags!',
|
||||
@@ -217,14 +77,18 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Featured tags',
|
||||
},
|
||||
featuredTagsDescription: {
|
||||
id: 'project.settings.tags.featured-tags-description',
|
||||
id: 'project.settings.tags.featured-tags-select-description',
|
||||
defaultMessage:
|
||||
'You can feature up to 3 of your most relevant tags. Other tags may be promoted to featured if you do not select all 3.',
|
||||
'Select your most relevant tags. These are displayed before the rest of your tags.',
|
||||
},
|
||||
selectAtLeastOneCategory: {
|
||||
id: 'project.settings.tags.select-at-least-one-category',
|
||||
defaultMessage: 'Select at least one category in order to feature a category.',
|
||||
},
|
||||
featuredTagsRequired: {
|
||||
id: 'project.settings.tags.featured-tags-required',
|
||||
defaultMessage: 'You must have at least one featured tag.',
|
||||
},
|
||||
tooManyTagsServerHardWarning: {
|
||||
id: 'project.settings.tags.too-many-tags-server-hard-warning',
|
||||
defaultMessage:
|
||||
@@ -252,74 +116,185 @@ const messages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const groupTitleMessages: Record<string, MessageDescriptor> = defineMessages({
|
||||
categories: {
|
||||
id: 'project.settings.tags.group-title.categories',
|
||||
defaultMessage: '{showType, select, yes {{types} categories} other {Categories}}',
|
||||
},
|
||||
features: {
|
||||
id: 'project.settings.tags.group-title.features',
|
||||
defaultMessage: '{showType, select, yes {{types} features} other {Features}}',
|
||||
},
|
||||
resolutions: {
|
||||
id: 'project.settings.tags.group-title.resolutions',
|
||||
defaultMessage: '{showType, select, yes {{types} resolutions} other {Resolutions}}',
|
||||
},
|
||||
'performance impact': {
|
||||
id: 'project.settings.tags.group-title.performance-impact',
|
||||
defaultMessage:
|
||||
'{showType, select, yes {{types} performance impact} other {Performance impact}}',
|
||||
},
|
||||
})
|
||||
|
||||
const groupDescriptionMessages: Record<string, MessageDescriptor> = {
|
||||
categories: messages.categoriesDescription,
|
||||
features: messages.featuresDescription,
|
||||
resolutions: messages.resolutionsDescription,
|
||||
'performance impact': messages.performanceImpactDescription,
|
||||
}
|
||||
|
||||
const { projectV2: project, projectV3, patchProject } = injectProjectPageContext()
|
||||
|
||||
useProjectSettingsHeadTitle(messages.tagsTitle)
|
||||
|
||||
const formatCategoryName = (categoryName: string) => {
|
||||
return formatCategory(formatMessage, categoryName)
|
||||
}
|
||||
const formatCategoryName = (categoryName: string) => formatCategory(formatMessage, categoryName)
|
||||
|
||||
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
||||
|
||||
const getTagIcon = (categoryName: string) => {
|
||||
const iconName = isServerProject.value
|
||||
? (SERVER_CATEGORY_ICON_MAP[categoryName] ?? categoryName)
|
||||
: categoryName
|
||||
return getCategoryIcon(iconName)
|
||||
const canSelectTags = computed(() => project.value.versions.length > 0 || isServerProject.value)
|
||||
|
||||
const projectTypes = computed(() => {
|
||||
if (isServerProject.value) {
|
||||
return ['minecraft_java_server']
|
||||
}
|
||||
|
||||
const types = projectV3.value?.project_types?.length
|
||||
? projectV3.value.project_types
|
||||
: [project.value.actualProjectType]
|
||||
|
||||
return sortProjectTypes(new Set(types))
|
||||
})
|
||||
|
||||
const allCategories = computed(
|
||||
() => sortedCategories(tags.value, formatCategoryName, locale.value) as Category[],
|
||||
)
|
||||
|
||||
const projectTypesByCategoryList = computed(() => {
|
||||
const lists = new Map<string, string[]>()
|
||||
for (const projectType of projectTypes.value) {
|
||||
const source = SHARED_CATEGORY_PROJECT_TYPES[projectType] ?? projectType
|
||||
lists.set(source, [...(lists.get(source) ?? []), projectType])
|
||||
}
|
||||
return lists
|
||||
})
|
||||
|
||||
const projectTypeListFormatter = computed(
|
||||
() => new Intl.ListFormat(locale.value, { style: 'long', type: 'conjunction' }),
|
||||
)
|
||||
|
||||
function formatProjectTypeName(type: string) {
|
||||
return formatProjectTypeSentence(
|
||||
formatMessage,
|
||||
type === 'minecraft_java_server' ? 'server' : type,
|
||||
)
|
||||
}
|
||||
|
||||
const matchesProjectType = (x: Category) => {
|
||||
if (isServerProject.value) {
|
||||
return x.project_type === 'minecraft_java_server'
|
||||
} else {
|
||||
return x.project_type === project.value.actualProjectType
|
||||
function formatGroupTitle(header: string, types: string[]) {
|
||||
const message = groupTitleMessages[header]
|
||||
if (!message) {
|
||||
return formatCategoryHeader(formatMessage, header)
|
||||
}
|
||||
|
||||
const showType = projectTypes.value.length > 1
|
||||
return formatMessage(message, {
|
||||
showType: showType ? 'yes' : 'other',
|
||||
types: showType
|
||||
? capitalizeString(projectTypeListFormatter.value.format(types.map(formatProjectTypeName)))
|
||||
: '',
|
||||
})
|
||||
}
|
||||
|
||||
function formatGroupDescription(header: string, types: string[]) {
|
||||
const message = groupDescriptionMessages[header]
|
||||
if (!message) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// listing out every type gets unwieldy, fallback to the generic term when there's more than one
|
||||
return formatMessage(message, {
|
||||
type: formatProjectTypeName(types.length > 1 ? 'project' : types[0]),
|
||||
})
|
||||
}
|
||||
|
||||
const categorySections = computed(() => {
|
||||
const sections: {
|
||||
categoryList: string
|
||||
header: string
|
||||
types: string[]
|
||||
categories: Category[]
|
||||
}[] = []
|
||||
|
||||
for (const [categoryList, types] of projectTypesByCategoryList.value) {
|
||||
const byHeader = new Map<string, Category[]>()
|
||||
for (const category of allCategories.value) {
|
||||
if (category.project_type !== categoryList) continue
|
||||
byHeader.set(category.header, [...(byHeader.get(category.header) ?? []), category])
|
||||
}
|
||||
|
||||
for (const [header, categories] of byHeader) {
|
||||
sections.push({
|
||||
categoryList,
|
||||
header,
|
||||
types,
|
||||
categories:
|
||||
header === 'minecraft_server_features' ? withPokemonFirst(categories) : categories,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return sections
|
||||
})
|
||||
|
||||
const categoryGroups = computed<CategoryGroup[]>(() =>
|
||||
categorySections.value.map((section) => ({
|
||||
id: `${section.categoryList}-${section.header}`,
|
||||
title: formatGroupTitle(section.header, section.types),
|
||||
description: formatGroupDescription(section.header, section.types),
|
||||
categories: section.categories,
|
||||
})),
|
||||
)
|
||||
|
||||
function withPokemonFirst(categories: Category[]) {
|
||||
return categories.slice().sort((a, b) => {
|
||||
if (a.name === 'pokemon') return -1
|
||||
if (b.name === 'pokemon') return 1
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
const availableTags = computed(() => [
|
||||
...new Set(categorySections.value.flatMap((section) => section.categories.map((x) => x.name))),
|
||||
])
|
||||
|
||||
function hasSameTags(a: string[], b: string[]) {
|
||||
return a.length === b.length && a.every((tag) => b.includes(tag))
|
||||
}
|
||||
|
||||
const { saved, current, saving, hasChanges, reset, save } = useSavable(
|
||||
() => ({
|
||||
selectedTags: sortedCategories(tags.value, formatCategoryName, locale.value).filter(
|
||||
(x: Category) =>
|
||||
matchesProjectType(x) &&
|
||||
(project.value.categories.includes(x.name) ||
|
||||
project.value.additional_categories.includes(x.name)),
|
||||
) as Category[],
|
||||
featuredTags: sortedCategories(tags.value, formatCategoryName, locale.value).filter(
|
||||
(x: Category) => matchesProjectType(x) && project.value.categories.includes(x.name),
|
||||
) as Category[],
|
||||
selectedTags: availableTags.value.filter(
|
||||
(tag) =>
|
||||
project.value.categories.includes(tag) || project.value.additional_categories.includes(tag),
|
||||
),
|
||||
featuredTags: availableTags.value.filter((tag) => project.value.categories.includes(tag)),
|
||||
}),
|
||||
async () => {
|
||||
// Promote selected categories to featured if there are less than 3 featured
|
||||
const newFeaturedTags = current.value.featuredTags.slice()
|
||||
if (newFeaturedTags.length < 1 && current.value.selectedTags.length > newFeaturedTags.length) {
|
||||
const nonFeaturedCategories = current.value.selectedTags.filter(
|
||||
(x) => !newFeaturedTags.includes(x),
|
||||
)
|
||||
nonFeaturedCategories
|
||||
.slice(0, Math.min(nonFeaturedCategories.length, 3 - newFeaturedTags.length))
|
||||
.forEach((x) => newFeaturedTags.push(x))
|
||||
if (!canSave.value) {
|
||||
throw new Error('At least one tag must be featured')
|
||||
}
|
||||
|
||||
// Convert selected and featured categories to backend-usable arrays
|
||||
const categories = newFeaturedTags.map((x) => x.name)
|
||||
const additionalCategories = current.value.selectedTags
|
||||
.filter((x) => !newFeaturedTags.includes(x))
|
||||
.map((x) => x.name)
|
||||
const featuredTags = current.value.featuredTags
|
||||
const additionalCategories = current.value.selectedTags.filter(
|
||||
(tag) => !featuredTags.includes(tag),
|
||||
)
|
||||
|
||||
const data: Record<string, string[]> = {}
|
||||
|
||||
if (
|
||||
categories.length !== project.value.categories.length ||
|
||||
categories.some((value) => !project.value.categories.includes(value))
|
||||
) {
|
||||
data.categories = categories
|
||||
if (!hasSameTags(featuredTags, project.value.categories)) {
|
||||
data.categories = featuredTags
|
||||
}
|
||||
|
||||
if (
|
||||
additionalCategories.length !== project.value.additional_categories.length ||
|
||||
additionalCategories.some((value) => !project.value.additional_categories.includes(value))
|
||||
) {
|
||||
if (!hasSameTags(additionalCategories, project.value.additional_categories)) {
|
||||
data.additional_categories = additionalCategories
|
||||
}
|
||||
|
||||
@@ -329,31 +304,15 @@ const { saved, current, saving, hasChanges, reset, save } = useSavable(
|
||||
|
||||
const { confirmLeaveModal } = usePageLeaveSafety(hasChanges)
|
||||
|
||||
const categoryLists = computed(() => {
|
||||
const lists: Record<string, Category[]> = {}
|
||||
sortedCategories(tags.value, formatCategoryName, locale.value).forEach((x: Category) => {
|
||||
if (matchesProjectType(x)) {
|
||||
const header = x.header
|
||||
if (!lists[header]) {
|
||||
lists[header] = []
|
||||
}
|
||||
lists[header].push(x)
|
||||
}
|
||||
})
|
||||
const featuresKey = 'minecraft_server_features'
|
||||
if (lists[featuresKey]) {
|
||||
lists[featuresKey].sort((a, b) => {
|
||||
if (a.name === 'pokemon') return -1
|
||||
if (b.name === 'pokemon') return 1
|
||||
return 0
|
||||
})
|
||||
}
|
||||
return lists
|
||||
})
|
||||
const isFeaturedLimitReached = computed(
|
||||
() => current.value.featuredTags.length >= MAX_FEATURED_TAGS,
|
||||
)
|
||||
|
||||
const canSave = computed(() => current.value.featuredTags.length > 0)
|
||||
|
||||
const tooManyTagsWarning = computed(() => {
|
||||
const tagCount = current.value.selectedTags.length
|
||||
if (projectV3?.value?.minecraft_server != null) {
|
||||
if (isServerProject.value) {
|
||||
if (tagCount > 18) {
|
||||
return formatMessage(messages.tooManyTagsServerHardWarning, { count: tagCount })
|
||||
} else if (tagCount > 12) {
|
||||
@@ -366,122 +325,140 @@ const tooManyTagsWarning = computed(() => {
|
||||
})
|
||||
|
||||
const multipleResolutionTagsWarning = computed(() => {
|
||||
if (project.value.actualProjectType !== 'resourcepack') return null
|
||||
if (!projectTypes.value.includes('resourcepack')) return null
|
||||
|
||||
const resolutionTags = current.value.selectedTags.filter((tag) =>
|
||||
['8x-', '16x', '32x', '48x', '64x', '128x', '256x', '512x+'].includes(tag.name),
|
||||
)
|
||||
const resolutionTags = current.value.selectedTags.filter((tag) => RESOLUTION_TAGS.includes(tag))
|
||||
if (resolutionTags.length < 2) return null
|
||||
|
||||
if (resolutionTags.length > 1) {
|
||||
const tagsList = resolutionTags
|
||||
.map((t) => t.name)
|
||||
return formatMessage(messages.multipleResolutionTagsWarning, {
|
||||
count: resolutionTags.length,
|
||||
tags: resolutionTags
|
||||
.join(', ')
|
||||
.replace('8x-', '8x or lower')
|
||||
.replace('512x+', '512x or higher')
|
||||
return formatMessage(messages.multipleResolutionTagsWarning, {
|
||||
count: resolutionTags.length,
|
||||
tags: tagsList,
|
||||
})
|
||||
}
|
||||
return null
|
||||
.replace('512x+', '512x or higher'),
|
||||
})
|
||||
})
|
||||
|
||||
const allTagsSelectedWarning = computed(() => {
|
||||
const categoriesForProjectType = sortedCategories(
|
||||
tags.value,
|
||||
formatCategoryName,
|
||||
locale.value,
|
||||
).filter((x: Category) => x.project_type === project.value.actualProjectType)
|
||||
const totalSelectedTags = current.value.selectedTags.length
|
||||
|
||||
if (
|
||||
totalSelectedTags === categoriesForProjectType.length &&
|
||||
categoriesForProjectType.length > 0
|
||||
availableTags.value.length < 1 ||
|
||||
availableTags.value.length < 1 ||
|
||||
current.value.selectedTags.length !== availableTags.value.length
|
||||
) {
|
||||
return formatMessage(messages.allTagsSelectedWarning, {
|
||||
count: categoriesForProjectType.length,
|
||||
})
|
||||
return null
|
||||
}
|
||||
return null
|
||||
|
||||
return formatMessage(messages.allTagsSelectedWarning, { count: availableTags.value.length })
|
||||
})
|
||||
|
||||
const toggleCategory = (category: Category) => {
|
||||
if (current.value.selectedTags.includes(category)) {
|
||||
current.value.selectedTags = current.value.selectedTags.filter((x) => x !== category)
|
||||
if (current.value.featuredTags.includes(category)) {
|
||||
current.value.featuredTags = current.value.featuredTags.filter((x) => x !== category)
|
||||
}
|
||||
} else {
|
||||
current.value.selectedTags = [...current.value.selectedTags, category]
|
||||
function toggleTagRaw(selection: string[], tag: string) {
|
||||
if (selection.includes(tag)) {
|
||||
return selection.filter((x) => x !== tag)
|
||||
}
|
||||
return availableTags.value.filter((x) => x === tag || selection.includes(x))
|
||||
}
|
||||
|
||||
const toggleTag = (tag: string) => {
|
||||
current.value.selectedTags = toggleTagRaw(current.value.selectedTags, tag)
|
||||
if (!current.value.selectedTags.includes(tag)) {
|
||||
current.value.featuredTags = current.value.featuredTags.filter((x) => x !== tag)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleFeaturedCategory = (category: Category) => {
|
||||
if (current.value.featuredTags.includes(category)) {
|
||||
current.value.featuredTags = current.value.featuredTags.filter((x) => x !== category)
|
||||
} else {
|
||||
current.value.featuredTags = [...current.value.featuredTags, category]
|
||||
}
|
||||
const toggleFeatured = (tag: string) => {
|
||||
current.value.featuredTags = toggleTagRaw(current.value.featuredTags, tag)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<ConfirmLeaveModal ref="confirmLeaveModal" />
|
||||
<h2 class="mb-4 mt-0 text-2xl font-semibold">
|
||||
{{ formatMessage(messages.title) }}
|
||||
</h2>
|
||||
<p v-if="!canSelectTags" class="known-errors">
|
||||
{{ formatMessage(messages.uploadVersionFirst) }}
|
||||
</p>
|
||||
<div v-else class="flex flex-col gap-4">
|
||||
<Admonition v-if="allTagsSelectedWarning" type="critical" :body="allTagsSelectedWarning" />
|
||||
<Admonition v-else-if="tooManyTagsWarning" type="warning" :body="tooManyTagsWarning" />
|
||||
<Admonition
|
||||
v-if="multipleResolutionTagsWarning"
|
||||
type="warning"
|
||||
:body="multipleResolutionTagsWarning"
|
||||
/>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.label__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-card-xs);
|
||||
margin-top: var(--spacing-card-bg);
|
||||
<div
|
||||
v-for="group in categoryGroups"
|
||||
:key="group.id"
|
||||
class="rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4"
|
||||
>
|
||||
<h3 class="mb-1 mt-0 text-base font-semibold text-contrast">
|
||||
{{ group.title }}
|
||||
</h3>
|
||||
<p v-if="group.description" class="mb-3 mt-0 text-sm">
|
||||
{{ group.description }}
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-4">
|
||||
<Checkbox
|
||||
v-for="category in group.categories"
|
||||
:key="`${group.id}-${category.name}`"
|
||||
:model-value="current.selectedTags.includes(category.name)"
|
||||
:description="formatCategoryName(category.name)"
|
||||
@update:model-value="toggleTag(category.name)"
|
||||
>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="category.name" enforce-type="category" />
|
||||
</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
svg {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
.category-list {
|
||||
column-count: 4;
|
||||
column-gap: var(--spacing-card-lg);
|
||||
margin-bottom: var(--spacing-card-md);
|
||||
|
||||
:deep(.category-selector) {
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
.category-selector__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
|
||||
.icon {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
margin-right: 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1250px) {
|
||||
column-count: 3;
|
||||
}
|
||||
@media only screen and (max-width: 1024px) {
|
||||
column-count: 4;
|
||||
}
|
||||
@media only screen and (max-width: 960px) {
|
||||
column-count: 3;
|
||||
}
|
||||
@media only screen and (max-width: 750px) {
|
||||
column-count: 2;
|
||||
}
|
||||
@media only screen and (max-width: 530px) {
|
||||
column-count: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4">
|
||||
<div class="mb-1 flex items-center gap-2">
|
||||
<h3 class="m-0 text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.featuredTags) }}
|
||||
</h3>
|
||||
<TagItem
|
||||
v-tooltip="
|
||||
current.featuredTags.length === 0
|
||||
? formatMessage(messages.featuredTagsRequired)
|
||||
: undefined
|
||||
"
|
||||
:style="canSave ? undefined : { '--_color': 'var(--color-red)' }"
|
||||
>
|
||||
{{ current.featuredTags.length }}/{{ MAX_FEATURED_TAGS }}
|
||||
</TagItem>
|
||||
</div>
|
||||
<p class="mb-3 mt-0 text-sm">
|
||||
{{ formatMessage(messages.featuredTagsDescription) }}
|
||||
</p>
|
||||
<p v-if="current.selectedTags.length < 1" class="m-0 text-sm text-secondary">
|
||||
{{ formatMessage(messages.selectAtLeastOneCategory) }}
|
||||
</p>
|
||||
<div v-else class="grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-4">
|
||||
<Checkbox
|
||||
v-for="name in current.selectedTags"
|
||||
:key="`featured-${name}`"
|
||||
:model-value="current.featuredTags.includes(name)"
|
||||
:description="formatCategoryName(name)"
|
||||
:disabled="isFeaturedLimitReached && !current.featuredTags.includes(name)"
|
||||
@update:model-value="toggleFeatured(name)"
|
||||
>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="name" enforce-type="category" />
|
||||
</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UnsavedChangesPopup
|
||||
:original="saved"
|
||||
:modified="current"
|
||||
:saving="saving"
|
||||
:can-save="canSave"
|
||||
:save-disabled-reason="messages.featuredTagsRequired"
|
||||
@reset="reset"
|
||||
@save="save"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -144,6 +144,22 @@
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.downloadButton) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
v-if="
|
||||
!!primaryFile?.url &&
|
||||
isStaff(auth.user) &&
|
||||
modSettings.get(moderationSettings.General.SlicerButtonInVersions)
|
||||
"
|
||||
v-tooltip="`Open in Slicer`"
|
||||
type="quiet"
|
||||
target="_blank"
|
||||
:href="`https://slicer.run/?url=${encodeURIComponent(primaryFile?.url)}`"
|
||||
class="!bg-button-bg"
|
||||
aria-label="Open in Slicer"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
Open
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
v-for="file in promotedFiles.filter(
|
||||
(x) =>
|
||||
@@ -502,6 +518,7 @@ import {
|
||||
TrashIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { moderationSettings } from '@modrinth/moderation'
|
||||
import {
|
||||
Admonition,
|
||||
Button,
|
||||
@@ -545,6 +562,7 @@ const emit = defineEmits<{
|
||||
const data = useNuxtApp()
|
||||
const route = useNativeRoute()
|
||||
const router = useRouter()
|
||||
const modSettings = useModerationSettings()
|
||||
const auth = await useAuth()
|
||||
const tags = useGeneratedState()
|
||||
const client = injectModrinthClient()
|
||||
|
||||
@@ -53,6 +53,21 @@
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
v-if="
|
||||
!!getPrimaryFile(version) &&
|
||||
isStaff(auth.user) &&
|
||||
modSettings.get(moderationSettings.General.SlicerButtonInVersions)
|
||||
"
|
||||
v-tooltip="`Open in Slicer`"
|
||||
type="quiet"
|
||||
target="_blank"
|
||||
:href="`https://slicer.run/?url=${encodeURIComponent(createDownloadUrl(version))}`"
|
||||
class="!w-9 !rounded-full !px-0 hover:!bg-button-bg"
|
||||
aria-label="Open in Slicer"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</ButtonLink>
|
||||
<TeleportOverflowMenu
|
||||
v-if="currentMember"
|
||||
type="quiet"
|
||||
@@ -268,6 +283,7 @@ import {
|
||||
SpinnerIcon,
|
||||
TrashIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { moderationSettings } from '@modrinth/moderation'
|
||||
import {
|
||||
ButtonLink,
|
||||
ConfirmModal,
|
||||
@@ -277,6 +293,7 @@ import {
|
||||
ProjectPageVersions,
|
||||
TeleportOverflowMenu,
|
||||
} from '@modrinth/ui'
|
||||
import { isStaff } from '@modrinth/utils'
|
||||
import { onMounted, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
|
||||
@@ -289,6 +306,7 @@ const { createProjectDownloadUrl, updateVersionsFilterContext } = useCdnDownload
|
||||
|
||||
const tags = useGeneratedState()
|
||||
const flags = useFeatureFlags()
|
||||
const modSettings = useModerationSettings()
|
||||
const auth = await useAuth()
|
||||
|
||||
const client = injectModrinthClient()
|
||||
|
||||
@@ -533,7 +533,7 @@ useSeoMeta({
|
||||
<div>
|
||||
<div class="landing-hero">
|
||||
<div
|
||||
class="relative mt-4 h-fit w-fit rounded-full bg-highlight-green px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
class="relative mt-4 h-fit w-fit rounded-full bg-brand-highlight px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
>
|
||||
{{ formatMessage(commonMessages.betaRelease) }}
|
||||
</div>
|
||||
|
||||
@@ -425,6 +425,7 @@ import {
|
||||
ProjectCardList,
|
||||
RadioButtons,
|
||||
SidebarCard,
|
||||
sortProjectTypes,
|
||||
StyledInput,
|
||||
TeleportOverflowMenu,
|
||||
useCompactNumber,
|
||||
@@ -769,7 +770,7 @@ const projectTypes = computed(() => {
|
||||
projects.value?.map((project) => project?.project_type).filter((x) => x !== undefined) || [],
|
||||
)
|
||||
projectSet.delete('project')
|
||||
return Array.from(projectSet)
|
||||
return sortProjectTypes(projectSet)
|
||||
})
|
||||
|
||||
function getProjectTypeSentenceMessage(type) {
|
||||
|
||||
@@ -159,22 +159,22 @@
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M319.052 54.2233C319.058 37.6952 315.689 21.3441 309.156 6.19864C302.624 -8.94682 293.07 -22.559 281.094 -33.7816C269.119 -45.0042 254.982 -53.5944 239.573 -59.012C224.164 -64.4295 207.815 -66.5571 191.556 -65.2609C175.297 -63.9648 159.479 -59.2729 145.097 -51.4805C130.715 -43.688 118.08 -32.9636 107.987 -19.9818C97.8942 -6.99995 90.5617 7.95837 86.4509 23.9523C82.3401 39.9462 81.5398 56.6297 84.1004 72.9533L103.415 67.7101C100.452 45.7823 104.805 23.4811 115.783 4.35031C126.761 -14.7805 143.734 -29.6435 164.005 -37.8768C184.275 -46.1102 206.681 -47.2415 227.661 -41.0911C248.641 -34.9407 266.991 -21.8613 279.797 -3.93146L262.376 6.25239C255.476 -2.83248 246.698 -10.2779 236.659 -15.5617C226.619 -20.8455 215.561 -23.8398 204.26 -24.3345L206.032 -3.60929C217.266 -2.58081 227.949 1.79213 236.737 8.95915C245.524 16.1262 252.024 25.767 255.418 36.6684C258.812 47.5697 258.949 59.2444 255.81 70.223C252.672 81.2017 246.398 90.9937 237.78 98.3668L248.048 116.384C261.575 105.867 271.303 91.124 275.725 74.437C280.146 57.7501 279.016 40.0505 272.507 24.079L289.873 13.9453C295.192 26.0533 298.028 39.1299 298.209 52.3816L319.052 54.2233Z"
|
||||
fill="#1BD96A"
|
||||
fill="var(--color-brand)"
|
||||
fill-opacity="0.16"
|
||||
/>
|
||||
<path
|
||||
d="M145.331 -51.0364C159.653 -58.796 175.404 -63.4691 191.595 -64.7598C207.786 -66.0504 224.065 -63.9308 239.41 -58.5361C254.754 -53.1414 268.832 -44.5878 280.757 -33.4124C292.682 -22.2369 302.196 -8.68194 308.702 6.39988C315.134 21.3138 318.483 37.4019 318.551 53.6733L298.696 51.919C298.455 38.7552 295.611 25.7724 290.326 13.7409L290.103 13.2307L289.625 13.5089L272.26 23.6428L271.882 23.8634L272.048 24.2711C278.514 40.1402 279.638 57.7262 275.245 74.3061C270.901 90.7012 261.401 105.207 248.194 115.632L238.415 98.4753C246.945 91.0702 253.16 81.3016 256.287 70.3626C259.453 59.2887 259.315 47.5128 255.892 36.5169C252.469 25.5209 245.912 15.7962 237.048 8.56694C228.292 1.42595 217.67 -2.9636 206.491 -4.06957L204.803 -23.8035C215.835 -23.2382 226.622 -20.2771 236.429 -15.1154L236.43 -15.1156C246.405 -9.8657 255.126 -2.46736 261.982 6.5593L262.247 6.9086L262.624 6.68831L280.046 -3.49542L280.522 -3.7744L280.2 -4.2262C267.329 -22.247 248.885 -35.3926 227.798 -41.5743C206.712 -47.7558 184.193 -46.6194 163.82 -38.3444C143.447 -30.0694 126.388 -15.1307 115.354 4.09694C104.394 23.1968 100.004 45.441 102.865 67.338L84.5078 72.3214C82.0618 56.2426 82.8841 39.8252 86.9313 24.0789C91.0248 8.15219 98.3266 -6.74338 108.377 -19.6706C118.427 -32.5979 131.01 -43.2767 145.331 -51.0364Z"
|
||||
stroke="#1BD96A"
|
||||
stroke="var(--color-brand)"
|
||||
stroke-opacity="0.12"
|
||||
/>
|
||||
<path
|
||||
d="M260.003 157.491C244.923 166.318 228.106 171.665 210.752 173.15C193.397 174.636 175.935 172.223 159.61 166.084C143.286 159.945 128.503 150.231 116.318 137.637C104.132 125.042 94.8439 109.878 89.1171 93.226L108.448 87.9782C110.395 93.3018 112.784 98.4486 115.59 103.363C118.525 108.52 121.913 113.398 125.713 117.939L140.152 102.814C131.996 92.3742 126.591 80.0086 124.444 66.8751C122.296 53.7416 123.476 40.2699 127.873 27.7219C132.27 15.1739 139.74 3.96017 149.584 -4.86882C159.427 -13.6978 171.322 -19.8532 184.154 -22.7584L185.891 -2.02536C177.437 0.311457 169.624 4.57902 163.052 10.4495C156.48 16.3201 151.323 23.6373 147.979 31.8393C144.634 40.0412 143.191 48.9096 143.759 57.7633C144.327 66.6169 146.892 75.2202 151.257 82.9123C152.243 84.6198 153.258 86.3022 154.382 87.7452L172.854 68.4135L161.573 52.4568L176.638 25.2047L201.398 12.3472L211.468 19.805L202.636 35.5055L192.974 41.7298L187.498 51.6422L193.955 61.0422C193.955 61.0422 203.56 67.0702 203.576 67.0659L213.41 61.3547L218.72 50.9454L233.753 41.2004L241.537 51.0445L230.214 76.6512L204.201 93.4501L187.642 82.5445L169.003 102.096C176.464 107.133 184.988 110.331 193.89 111.432C202.792 112.534 211.826 111.509 220.268 108.44L230.553 126.503C218.179 131.679 204.694 133.531 191.407 131.879C178.121 130.227 165.481 125.128 154.715 117.075L140.327 132.134C153.557 142.488 169.184 149.244 185.722 151.759C202.26 154.274 219.16 152.465 234.815 146.503C250.471 140.542 264.362 130.626 275.169 117.699C285.976 104.771 293.339 89.2611 296.56 72.6427L317.419 74.4794C314.438 91.7283 307.75 108.104 297.828 122.449C287.906 136.794 274.993 148.756 260.003 157.491Z"
|
||||
fill="#1BD96A"
|
||||
fill="var(--color-brand)"
|
||||
fill-opacity="0.16"
|
||||
/>
|
||||
<path
|
||||
d="M149.913 -4.49238C159.551 -13.1371 171.169 -19.2006 183.706 -22.1377L185.36 -2.39778C176.987 -0.0177107 169.248 4.24324 162.723 10.0719C156.094 15.9933 150.893 23.3739 147.519 31.6468C144.146 39.9199 142.69 48.8658 143.263 57.7963C143.837 66.7266 146.424 75.4045 150.826 83.1632L150.828 83.1668C151.816 84.8756 152.845 86.5845 153.993 88.0571L154.344 88.5081L154.739 88.0956L173.211 68.7634L173.5 68.4621L173.258 68.1208L162.16 52.4243L176.998 25.5829L201.351 12.9363L210.816 19.9464L202.265 35.1485L192.707 41.3047L192.602 41.373L192.541 41.4842L187.064 51.3967L186.912 51.6708L187.09 51.9298L193.547 61.3299L193.606 61.4157L193.693 61.4702L193.7 61.4744C193.705 61.4773 193.712 61.4815 193.721 61.4871C193.739 61.4985 193.766 61.5158 193.801 61.5377C193.871 61.5819 193.975 61.6461 194.106 61.7285C194.369 61.8933 194.744 62.1297 195.194 62.4122C196.095 62.9772 197.296 63.7303 198.498 64.4836C199.7 65.2368 200.903 65.9902 201.806 66.5549C202.257 66.8371 202.634 67.0726 202.898 67.2372C203.03 67.3195 203.136 67.3844 203.208 67.4289C203.244 67.4509 203.273 67.4687 203.293 67.4811C203.303 67.487 203.312 67.4932 203.321 67.498C203.324 67.5001 203.332 67.5044 203.341 67.5089C203.344 67.5108 203.354 67.516 203.367 67.522C203.375 67.5257 203.397 67.5353 203.411 67.5406C203.444 67.5507 203.59 67.5693 203.705 67.5525L203.767 67.5355L203.823 67.5021L213.656 61.7911L213.783 61.7179L213.851 61.5856L219.099 51.2968L233.644 41.8683L240.959 51.1188L229.821 76.3076L204.202 92.8511L187.912 82.1225L187.569 81.8961L187.285 82.1952L168.646 101.746L168.233 102.18L168.728 102.515C176.254 107.596 184.85 110.821 193.829 111.932C202.671 113.026 211.64 112.038 220.043 109.052L229.836 126.252C217.685 131.229 204.482 132.997 191.468 131.379C178.266 129.738 165.708 124.671 155.01 116.67L154.66 116.409L154.358 116.725L139.97 131.784L139.584 132.189L140.024 132.532C153.321 142.939 169.026 149.729 185.648 152.257C202.269 154.785 219.255 152.966 234.99 146.974C250.724 140.983 264.686 131.017 275.548 118.024C286.313 105.146 293.676 89.7174 296.957 73.1823L316.833 74.9331C313.819 91.9106 307.198 108.026 297.421 122.16C287.541 136.444 274.683 148.357 259.755 157.055L259.754 157.055C244.738 165.845 227.991 171.169 210.71 172.648C193.429 174.128 176.039 171.725 159.783 165.612C143.528 159.499 128.806 149.826 116.672 137.284C104.662 124.872 95.4819 109.951 89.7657 93.5707L108.141 88.5822C109.945 93.4461 112.116 98.1617 114.637 102.686L115.16 103.615C118.11 108.797 121.515 113.7 125.333 118.264L125.689 118.688L126.07 118.289L140.509 103.163L140.811 102.847L140.541 102.501C132.438 92.1288 127.067 79.8422 124.933 66.7929C122.799 53.7434 123.973 40.3579 128.341 27.8902C132.71 15.4225 140.132 4.28013 149.913 -4.49238Z"
|
||||
stroke="#1BD96A"
|
||||
stroke="var(--color-brand)"
|
||||
stroke-opacity="0.12"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { getMarginTarget } from '@modrinth/moderation'
|
||||
import { commonProjectTypeCategoryMessages, NavTabs, useVIntl } from '@modrinth/ui'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
const route = useRoute()
|
||||
const modSettings = useModerationSettings()
|
||||
|
||||
const allowTabChanging = computed(() => !route.query.sid)
|
||||
const marginTarget = computed(() => getMarginTarget(modSettings.value))
|
||||
|
||||
const selectableProjectTypes = [
|
||||
{
|
||||
@@ -47,7 +50,10 @@ const selectableProjectTypes = [
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="mx-auto box-border flex w-full max-w-[1280px] flex-col gap-4 px-6 pb-6">
|
||||
<div
|
||||
class="box-border flex w-full max-w-[1280px] flex-col gap-4 px-6 pb-6"
|
||||
:class="`m${marginTarget}-auto`"
|
||||
>
|
||||
<NavTabs
|
||||
v-if="!flags.projectTypesPrimaryNav && allowTabChanging"
|
||||
:links="selectableProjectTypes"
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="z-[5] flex w-full flex-col gap-8">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
class="relative h-fit w-fit rounded-full bg-highlight-green px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
class="relative h-fit w-fit rounded-full bg-brand-highlight px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
>
|
||||
{{ formatMessage(commonMessages.betaRelease) }}
|
||||
</div>
|
||||
@@ -110,7 +110,7 @@
|
||||
<div class="faded-brand-line absolute left-0 top-0 h-[1px] w-full"></div>
|
||||
<div class="relative mx-auto flex w-full max-w-7xl flex-col gap-8">
|
||||
<div
|
||||
class="relative w-fit rounded-full bg-highlight-green px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
class="relative w-fit rounded-full bg-brand-highlight px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
>
|
||||
{{ formatMessage(messages.whyModrinthHosting) }}
|
||||
</div>
|
||||
@@ -154,7 +154,7 @@
|
||||
</div>
|
||||
|
||||
<div class="relative flex flex-col gap-4 rounded-2xl bg-bg p-6 text-left md:p-12">
|
||||
<LoaderIcon loader="fabric" class="size-8 text-brand" />
|
||||
<TagIcon tag="fabric" enforce-type="loader" class="size-8 text-brand" />
|
||||
<h2 class="m-0 text-lg font-bold">{{ formatMessage(messages.yourFavoriteMods) }}</h2>
|
||||
<h3 class="m-0 text-base font-normal text-secondary">
|
||||
{{ formatMessage(messages.yourFavoriteModsDescription) }}
|
||||
@@ -238,7 +238,7 @@
|
||||
<div class="faded-brand-line absolute left-0 top-0 h-[1px] w-full"></div>
|
||||
<div class="relative mx-auto flex w-full max-w-7xl flex-col gap-8">
|
||||
<div
|
||||
class="relative w-fit rounded-full bg-highlight-green px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
class="relative w-fit rounded-full bg-brand-highlight px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
>
|
||||
{{ formatMessage(messages.includedWithYourServer) }}
|
||||
</div>
|
||||
@@ -641,9 +641,9 @@ import {
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
LoaderIcon,
|
||||
ModrinthServersPurchaseModal,
|
||||
OptionGroup,
|
||||
TagIcon,
|
||||
useFormatPrice,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="relative mx-auto mb-6 flex min-h-screen w-full max-w-[1280px] flex-col px-6">
|
||||
<div
|
||||
class="relativemb-6 flex min-h-screen w-full max-w-[1280px] flex-col px-6"
|
||||
:class="`m${marginTarget}-auto`"
|
||||
>
|
||||
<h1>Moderation</h1>
|
||||
<NavTabs :links="moderationLinks" class="mb-4 hidden sm:flex" />
|
||||
<div class="mb-4 sm:hidden">
|
||||
@@ -16,6 +19,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FolderIcon, GlobeIcon, HashIcon, ReportIcon, ShieldCheckIcon } from '@modrinth/assets'
|
||||
import { getMarginTarget } from '@modrinth/moderation'
|
||||
import { Chips, defineMessages, NavTabs, useVIntl } from '@modrinth/ui'
|
||||
|
||||
definePageMeta({
|
||||
@@ -30,6 +34,9 @@ const { formatMessage } = useVIntl()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const modSettings = useModerationSettings()
|
||||
const marginTarget = computed(() => getMarginTarget(modSettings.value))
|
||||
|
||||
const messages = defineMessages({
|
||||
projectsTitle: {
|
||||
id: 'moderation.page.projects',
|
||||
|
||||
@@ -12,6 +12,7 @@ import ModerationTechRevCard from '~/components/ui/moderation/ModerationTechRevC
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
const route = useRoute()
|
||||
const keybinds = useModerationKeybinds()
|
||||
|
||||
const projectId = String(useRouteId('project'))
|
||||
|
||||
@@ -269,6 +270,34 @@ function handleShowMaliciousSummary(unsafeFiles: UnsafeFile[]) {
|
||||
function refetch() {
|
||||
queryClient.invalidateQueries({ queryKey: ['tech-review-project-report', projectId] })
|
||||
}
|
||||
|
||||
function handleKeybinds(event: KeyboardEvent) {
|
||||
keybinds.value.handle(event, {
|
||||
scope: 'tech-review',
|
||||
actions: {
|
||||
goToTop: () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
},
|
||||
goToBottom: () => {
|
||||
window.scrollTo({
|
||||
top: document.body.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeybinds)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeybinds)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -32,6 +32,7 @@ useHead({ title: 'Tech review queue - Modrinth' })
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
const keybinds = useModerationKeybinds()
|
||||
|
||||
const currentPage = ref(1)
|
||||
const API_PAGE_SIZE = 50
|
||||
@@ -571,6 +572,50 @@ watch(totalPages, (pages) => {
|
||||
// complete: 20,
|
||||
// }
|
||||
// })
|
||||
|
||||
const CARD_BOTTOM_OFFSET = 210
|
||||
|
||||
function handleKeybinds(event: KeyboardEvent) {
|
||||
keybinds.value.handle(event, {
|
||||
scope: 'tech-review',
|
||||
actions: {
|
||||
goToTop: () => {
|
||||
Array.from(cardRefs.values())
|
||||
.filter((card) => card.getBoundingClientRect().top <= 0)
|
||||
.reduce((prev, curr) =>
|
||||
curr.getBoundingClientRect().top > prev.getBoundingClientRect().top ? curr : prev,
|
||||
)
|
||||
?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
},
|
||||
goToBottom: () => {
|
||||
const nearestCard = Array.from(cardRefs.values())
|
||||
.filter((card) => card.getBoundingClientRect().bottom >= window.innerHeight)
|
||||
.reduce((prev, curr) =>
|
||||
curr.getBoundingClientRect().top < prev.getBoundingClientRect().top ? curr : prev,
|
||||
)
|
||||
|
||||
if (nearestCard) {
|
||||
window.scrollTo({
|
||||
behavior: 'smooth',
|
||||
top:
|
||||
nearestCard.getBoundingClientRect().bottom +
|
||||
window.scrollY -
|
||||
window.innerHeight +
|
||||
CARD_BOTTOM_OFFSET,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeybinds)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeybinds)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -236,6 +236,7 @@ import {
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
SidebarCard,
|
||||
sortProjectTypes,
|
||||
useCompactNumber,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
@@ -386,15 +387,14 @@ const isInvited = computed(() => {
|
||||
})
|
||||
|
||||
const projectTypes = computed(() => {
|
||||
const obj: Record<string, boolean> = {}
|
||||
const types = new Set<string>()
|
||||
|
||||
for (const project of projects.value ?? []) {
|
||||
obj[project.project_types[0] ?? 'project'] = true
|
||||
const type = project.project_types[0] ?? 'project'
|
||||
if (type !== 'project') types.add(type)
|
||||
}
|
||||
|
||||
delete obj.project
|
||||
|
||||
return Object.keys(obj)
|
||||
return sortProjectTypes(types)
|
||||
})
|
||||
function isProjectServer(project: ProjectV3): boolean {
|
||||
return project.minecraft_server != null
|
||||
@@ -504,15 +504,12 @@ const navLinks = computed(() => [
|
||||
label: formatMessage(commonMessages.allProjectType),
|
||||
href: `/organization/${organization.value?.slug}`,
|
||||
},
|
||||
...projectTypes.value
|
||||
.map((x) => {
|
||||
return {
|
||||
label: formatMessage(getProjectTypeMessage(x as ProjectType, true)),
|
||||
href: `/organization/${organization.value?.slug}/${x}s`,
|
||||
}
|
||||
})
|
||||
.slice()
|
||||
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||
...projectTypes.value.map((x) => {
|
||||
return {
|
||||
label: formatMessage(getProjectTypeMessage(x as ProjectType, true)),
|
||||
href: `/organization/${organization.value?.slug}/${x}s`,
|
||||
}
|
||||
}),
|
||||
])
|
||||
|
||||
async function copyId() {
|
||||
|
||||
@@ -35,6 +35,9 @@ const userProfile = provideUserProfile({
|
||||
getOrganizations: (userId) => client.labrinth.users_v2.getOrganizations(userId),
|
||||
getCollections: (userId) => client.labrinth.users_v2.getCollections(userId),
|
||||
patchUser: (userId, patch) => client.labrinth.users_v2.patch(userId, patch),
|
||||
changeAvatar: (userId, file, extension) =>
|
||||
client.labrinth.users_v2.changeIcon(userId, file, extension),
|
||||
deleteAvatar: (userId) => client.labrinth.users_v2.deleteIcon(userId),
|
||||
getBlockedUsers: () => client.labrinth.blocked_users_v3.list(),
|
||||
blockUser: (userId) => client.labrinth.blocked_users_v3.block(userId),
|
||||
unblockUser: (userId) => client.labrinth.blocked_users_v3.unblock(userId),
|
||||
|
||||
Reference in New Issue
Block a user