mirror of
https://github.com/modrinth/code.git
synced 2026-09-05 06:19:11 +00:00
feat: hide validation error when field has been edited
This commit is contained in:
@@ -28,8 +28,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { LightBulbIcon, TriangleAlertIcon, XCircleIcon } from '@modrinth/assets'
|
import { LightBulbIcon, TriangleAlertIcon, XCircleIcon } from '@modrinth/assets'
|
||||||
import type { FieldValidationMessage } from '@modrinth/moderation'
|
import type { FieldValidationMessage } from '@modrinth/moderation'
|
||||||
import { useVIntl } from '@modrinth/ui'
|
import { injectProjectPageContext, useVIntl } from '@modrinth/ui'
|
||||||
import { computed, onScopeDispose, shallowRef, watch } from 'vue'
|
import { computed, onScopeDispose, ref, shallowRef, watch } from 'vue'
|
||||||
|
|
||||||
type ValidationCheck = Omit<FieldValidationMessage, 'code'> & { code?: string }
|
type ValidationCheck = Omit<FieldValidationMessage, 'code'> & { code?: string }
|
||||||
|
|
||||||
@@ -39,6 +39,8 @@ const props = withDefaults(
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
check?: ValidationCheckInput
|
check?: ValidationCheckInput
|
||||||
debounce?: number
|
debounce?: number
|
||||||
|
projectField?: unknown
|
||||||
|
currentField?: unknown
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
check: null,
|
check: null,
|
||||||
@@ -47,31 +49,65 @@ const props = withDefaults(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const { projectValidationLoading } = injectProjectPageContext()
|
||||||
const displayedCheck = shallowRef<ValidationCheckInput>(props.check)
|
const displayedCheck = shallowRef<ValidationCheckInput>(props.check)
|
||||||
|
const validationIsStale = ref(props.projectField !== props.currentField)
|
||||||
let debounceTimer: ReturnType<typeof setTimeout> | undefined
|
let debounceTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
let validationRefreshCompleted = false
|
||||||
|
|
||||||
|
function updateDisplayedCheck(check: ValidationCheckInput) {
|
||||||
|
clearTimeout(debounceTimer)
|
||||||
|
const update = () => {
|
||||||
|
displayedCheck.value = check
|
||||||
|
if (
|
||||||
|
validationRefreshCompleted &&
|
||||||
|
!projectValidationLoading.value &&
|
||||||
|
props.projectField === props.currentField
|
||||||
|
) {
|
||||||
|
validationIsStale.value = false
|
||||||
|
validationRefreshCompleted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.debounce <= 0) {
|
||||||
|
update()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
debounceTimer = setTimeout(update, props.debounce)
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.check,
|
() => [props.projectField, props.currentField] as const,
|
||||||
(check) => {
|
([projectField, currentField], [previousProjectField]) => {
|
||||||
clearTimeout(debounceTimer)
|
if (projectField !== currentField) {
|
||||||
if (props.debounce <= 0) {
|
validationIsStale.value = true
|
||||||
displayedCheck.value = check
|
validationRefreshCompleted = false
|
||||||
return
|
} else if (projectField === previousProjectField) {
|
||||||
|
validationIsStale.value = false
|
||||||
|
validationRefreshCompleted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
debounceTimer = setTimeout(() => {
|
|
||||||
displayedCheck.value = check
|
|
||||||
}, props.debounce)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(projectValidationLoading, (loading, wasLoading) => {
|
||||||
|
if (!loading && wasLoading) {
|
||||||
|
validationRefreshCompleted = true
|
||||||
|
updateDisplayedCheck(props.check)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.check, updateDisplayedCheck)
|
||||||
|
|
||||||
onScopeDispose(() => clearTimeout(debounceTimer))
|
onScopeDispose(() => clearTimeout(debounceTimer))
|
||||||
|
|
||||||
const validations = computed(() =>
|
const validations = computed(() => {
|
||||||
Array.isArray(displayedCheck.value)
|
if (validationIsStale.value || projectValidationLoading.value) return []
|
||||||
|
|
||||||
|
return Array.isArray(displayedCheck.value)
|
||||||
? displayedCheck.value
|
? displayedCheck.value
|
||||||
: displayedCheck.value
|
: displayedCheck.value
|
||||||
? [displayedCheck.value]
|
? [displayedCheck.value]
|
||||||
: [],
|
: []
|
||||||
)
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -842,6 +842,14 @@ const messages = defineMessages({
|
|||||||
id: 'project.notification.updated.message',
|
id: 'project.notification.updated.message',
|
||||||
defaultMessage: 'Your project has been updated.',
|
defaultMessage: 'Your project has been updated.',
|
||||||
},
|
},
|
||||||
|
projectReviewSaveFailed: {
|
||||||
|
id: 'project.notification.review-save-failed.title',
|
||||||
|
defaultMessage: 'Failed to save project in review',
|
||||||
|
},
|
||||||
|
projectReviewSaveFailedDescription: {
|
||||||
|
id: 'project.notification.review-save-failed.description',
|
||||||
|
defaultMessage: 'You cannot save edits to your project which result in failing validation.',
|
||||||
|
},
|
||||||
reviewEnvironmentSettings: {
|
reviewEnvironmentSettings: {
|
||||||
id: 'project.environment.migration.review-button',
|
id: 'project.environment.migration.review-button',
|
||||||
defaultMessage: 'Review environment settings',
|
defaultMessage: 'Review environment settings',
|
||||||
@@ -1377,6 +1385,30 @@ function mergeV3ProjectPatch(old, data) {
|
|||||||
return merged
|
return merged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PROJECT_REVIEW_VALIDATION_ERROR =
|
||||||
|
'project must have no required validation nags before or while under review'
|
||||||
|
|
||||||
|
function addProjectMutationErrorNotification(error) {
|
||||||
|
const description =
|
||||||
|
error?.v1Error?.description ??
|
||||||
|
error?.responseData?.description ??
|
||||||
|
error?.data?.description ??
|
||||||
|
error?.message
|
||||||
|
const isProjectReviewValidationError = description === PROJECT_REVIEW_VALIDATION_ERROR
|
||||||
|
|
||||||
|
addNotification({
|
||||||
|
title: formatMessage(
|
||||||
|
isProjectReviewValidationError
|
||||||
|
? messages.projectReviewSaveFailed
|
||||||
|
: commonMessages.errorNotificationTitle,
|
||||||
|
),
|
||||||
|
text: isProjectReviewValidationError
|
||||||
|
? formatMessage(messages.projectReviewSaveFailedDescription)
|
||||||
|
: description,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Mutation for patching project data
|
// Mutation for patching project data
|
||||||
const patchProjectMutation = useMutation({
|
const patchProjectMutation = useMutation({
|
||||||
mutationFn: async ({ projectId, data }) => {
|
mutationFn: async ({ projectId, data }) => {
|
||||||
@@ -1412,11 +1444,7 @@ const patchProjectMutation = useMutation({
|
|||||||
if (context?.previousV3) {
|
if (context?.previousV3) {
|
||||||
queryClient.setQueryData(['project', 'v3', context.projectId], context.previousV3)
|
queryClient.setQueryData(['project', 'v3', context.projectId], context.previousV3)
|
||||||
}
|
}
|
||||||
addNotification({
|
addProjectMutationErrorNotification(err)
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
|
||||||
text: err.data ? err.data.description : err.message,
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSettled: async () => {
|
onSettled: async () => {
|
||||||
@@ -1447,11 +1475,7 @@ const patchStatusMutation = useMutation({
|
|||||||
if (context?.previousProject) {
|
if (context?.previousProject) {
|
||||||
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousProject)
|
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousProject)
|
||||||
}
|
}
|
||||||
addNotification({
|
addProjectMutationErrorNotification(err)
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
|
||||||
text: err.data ? err.data.description : err.message,
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSettled: async () => {
|
onSettled: async () => {
|
||||||
@@ -1491,11 +1515,7 @@ const patchProjectV3Mutation = useMutation({
|
|||||||
if (context?.previousV2) {
|
if (context?.previousV2) {
|
||||||
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousV2)
|
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousV2)
|
||||||
}
|
}
|
||||||
addNotification({
|
addProjectMutationErrorNotification(err)
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
|
||||||
text: err.data ? err.data.description : err.message,
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
@@ -1519,11 +1539,7 @@ const patchIconMutation = useMutation({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
addNotification({
|
addProjectMutationErrorNotification(err)
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
|
||||||
text: err.data ? err.data.description : err.message,
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSettled: async () => {
|
onSettled: async () => {
|
||||||
@@ -1572,11 +1588,7 @@ const createGalleryItemMutation = useMutation({
|
|||||||
if (context?.previousProject) {
|
if (context?.previousProject) {
|
||||||
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousProject)
|
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousProject)
|
||||||
}
|
}
|
||||||
addNotification({
|
addProjectMutationErrorNotification(err)
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
|
||||||
text: err.data ? err.data.description : err.message,
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSettled: async () => {
|
onSettled: async () => {
|
||||||
@@ -1625,11 +1637,7 @@ const editGalleryItemMutation = useMutation({
|
|||||||
if (context?.previousProject) {
|
if (context?.previousProject) {
|
||||||
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousProject)
|
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousProject)
|
||||||
}
|
}
|
||||||
addNotification({
|
addProjectMutationErrorNotification(err)
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
|
||||||
text: err.data ? err.data.description : err.message,
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSettled: async () => {
|
onSettled: async () => {
|
||||||
@@ -1662,11 +1670,7 @@ const deleteGalleryItemMutation = useMutation({
|
|||||||
if (context?.previousProject) {
|
if (context?.previousProject) {
|
||||||
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousProject)
|
queryClient.setQueryData(['project', 'v2', context.projectId], context.previousProject)
|
||||||
}
|
}
|
||||||
addNotification({
|
addProjectMutationErrorNotification(err)
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
|
||||||
text: err.data ? err.data.description : err.message,
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSettled: async () => {
|
onSettled: async () => {
|
||||||
|
|||||||
@@ -44,7 +44,11 @@
|
|||||||
:maxlength="64"
|
:maxlength="64"
|
||||||
placeholder="Enter title..."
|
placeholder="Enter title..."
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="galleryTitleValidation" />
|
<ValidationMessage
|
||||||
|
:check="galleryTitleValidation"
|
||||||
|
:project-field="filteredGallery[editIndex]?.title ?? ''"
|
||||||
|
:current-field="editTitle"
|
||||||
|
/>
|
||||||
<label for="gallery-image-desc">
|
<label for="gallery-image-desc">
|
||||||
<span class="label__title">Description</span>
|
<span class="label__title">Description</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -54,7 +58,11 @@
|
|||||||
:maxlength="255"
|
:maxlength="255"
|
||||||
placeholder="Enter description..."
|
placeholder="Enter description..."
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="galleryDescriptionValidation" />
|
<ValidationMessage
|
||||||
|
:check="galleryDescriptionValidation"
|
||||||
|
:project-field="filteredGallery[editIndex]?.description ?? ''"
|
||||||
|
:current-field="editDescription"
|
||||||
|
/>
|
||||||
<label for="gallery-image-ordering">
|
<label for="gallery-image-ordering">
|
||||||
<span class="label__title">Order Index</span>
|
<span class="label__title">Order Index</span>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -20,7 +20,12 @@
|
|||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
:on-image-upload="onUploadHandler"
|
:on-image-upload="onUploadHandler"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="descriptionValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="descriptionValidation"
|
||||||
|
:project-field="saved.description"
|
||||||
|
:current-field="current.description"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<UnsavedChangesPopup
|
<UnsavedChangesPopup
|
||||||
:original="saved"
|
:original="saved"
|
||||||
|
|||||||
@@ -294,6 +294,8 @@ const { confirmLeaveModal } = usePageLeaveSafety(hasChanges)
|
|||||||
</p>
|
</p>
|
||||||
<ValidationMessage
|
<ValidationMessage
|
||||||
:check="[...disclosureValidation, ...disclosureTextValidation]"
|
:check="[...disclosureValidation, ...disclosureTextValidation]"
|
||||||
|
:project-field="JSON.stringify(savedSnapshot)"
|
||||||
|
:current-field="JSON.stringify(currentSnapshot)"
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
<EmptyState
|
<EmptyState
|
||||||
|
|||||||
@@ -45,7 +45,12 @@
|
|||||||
:maxlength="64"
|
:maxlength="64"
|
||||||
placeholder="Enter title..."
|
placeholder="Enter title..."
|
||||||
/>
|
/>
|
||||||
<ValidationMessage class="mt-2" :check="galleryTitleValidation" />
|
<ValidationMessage
|
||||||
|
class="mt-2 max-w-[550px]"
|
||||||
|
:check="galleryTitleValidation"
|
||||||
|
:project-field="filteredGallery[editIndex]?.title ?? ''"
|
||||||
|
:current-field="editTitle"
|
||||||
|
/>
|
||||||
<label for="gallery-image-desc">
|
<label for="gallery-image-desc">
|
||||||
<span class="label__title">Description</span>
|
<span class="label__title">Description</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -55,7 +60,12 @@
|
|||||||
:maxlength="255"
|
:maxlength="255"
|
||||||
placeholder="Enter description..."
|
placeholder="Enter description..."
|
||||||
/>
|
/>
|
||||||
<ValidationMessage class="mt-2" :check="galleryDescriptionValidation" />
|
<ValidationMessage
|
||||||
|
class="mt-2 max-w-[550px]"
|
||||||
|
:check="galleryDescriptionValidation"
|
||||||
|
:project-field="filteredGallery[editIndex]?.description ?? ''"
|
||||||
|
:current-field="editDescription"
|
||||||
|
/>
|
||||||
<label for="gallery-image-ordering">
|
<label for="gallery-image-ordering">
|
||||||
<span class="label__title">Order Index</span>
|
<span class="label__title">Order Index</span>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -179,7 +179,12 @@ const placeholder = computed(() => placeholders[placeholderIndex.value] ?? place
|
|||||||
<div class="base-card block">
|
<div class="base-card block">
|
||||||
<div class="group relative float-end ml-4">
|
<div class="group relative float-end ml-4">
|
||||||
<IconSelect v-model="current.icon" />
|
<IconSelect v-model="current.icon" />
|
||||||
<ValidationMessage :check="iconValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="iconValidation"
|
||||||
|
:project-field="saved.icon"
|
||||||
|
:current-field="current.icon"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<SettingsLabel
|
<SettingsLabel
|
||||||
@@ -197,7 +202,12 @@ const placeholder = computed(() => placeholders[placeholderIndex.value] ?? place
|
|||||||
wrapper-class="flex-grow"
|
wrapper-class="flex-grow"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ValidationMessage :check="titleValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="titleValidation"
|
||||||
|
:project-field="saved.title"
|
||||||
|
:current-field="current.title"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<SettingsLabel
|
<SettingsLabel
|
||||||
@@ -213,7 +223,12 @@ const placeholder = computed(() => placeholders[placeholderIndex.value] ?? place
|
|||||||
:maxlength="120"
|
:maxlength="120"
|
||||||
wrapper-class="w-full"
|
wrapper-class="w-full"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="taglineValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="taglineValidation"
|
||||||
|
:project-field="saved.tagline"
|
||||||
|
:current-field="current.tagline"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4" @focusin="onSlugSuggestionFocusIn" @focusout="onSlugSuggestionFocusOut">
|
<div class="mt-4" @focusin="onSlugSuggestionFocusIn" @focusout="onSlugSuggestionFocusOut">
|
||||||
<SettingsLabel id="project-url" :title="messages.urlTitle" />
|
<SettingsLabel id="project-url" :title="messages.urlTitle" />
|
||||||
|
|||||||
@@ -29,7 +29,12 @@
|
|||||||
wrapper-class="w-full max-w-72"
|
wrapper-class="w-full max-w-72"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="nameValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="nameValidation"
|
||||||
|
:project-field="project.name"
|
||||||
|
:current-field="name"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div @focusin="onSlugSuggestionFocusIn" @focusout="onSlugSuggestionFocusOut">
|
<div @focusin="onSlugSuggestionFocusIn" @focusout="onSlugSuggestionFocusOut">
|
||||||
@@ -69,7 +74,12 @@
|
|||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
resize="vertical"
|
resize="vertical"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="summaryValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="summaryValidation"
|
||||||
|
:project-field="project.summary"
|
||||||
|
:current-field="summary"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -109,7 +119,12 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ValidationMessage :check="iconValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="iconValidation"
|
||||||
|
:project-field="false"
|
||||||
|
:current-field="Boolean(icon || deletedIcon)"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Server Project Settings -->
|
<!-- Server Project Settings -->
|
||||||
|
|||||||
@@ -36,7 +36,12 @@
|
|||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
trigger-type="base"
|
trigger-type="base"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="licenseSelectionValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="licenseSelectionValidation"
|
||||||
|
:project-field="project.license.id"
|
||||||
|
:current-field="licenseId"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,8 +90,16 @@
|
|||||||
:disabled="!hasPermission || licenseId === 'LicenseRef-Unknown'"
|
:disabled="!hasPermission || licenseId === 'LicenseRef-Unknown'"
|
||||||
wrapper-class="w-full"
|
wrapper-class="w-full"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="customLicenseValidation" />
|
<ValidationMessage
|
||||||
<ValidationMessage :check="effectiveLicenseCheck" />
|
:check="customLicenseValidation"
|
||||||
|
:project-field="project.license.id"
|
||||||
|
:current-field="licenseId"
|
||||||
|
/>
|
||||||
|
<ValidationMessage
|
||||||
|
:check="effectiveLicenseCheck"
|
||||||
|
:project-field="project.license.url ?? ''"
|
||||||
|
:current-field="current.licenseUrl"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,12 @@
|
|||||||
<!-- Server Project Links -->
|
<!-- Server Project Links -->
|
||||||
<section v-if="isServerProject" class="universal-card">
|
<section v-if="isServerProject" class="universal-card">
|
||||||
<h2>External links</h2>
|
<h2>External links</h2>
|
||||||
<ValidationMessage :check="externalLinksValidation" class="mb-4" />
|
<ValidationMessage
|
||||||
|
:check="externalLinksValidation"
|
||||||
|
:project-field="JSON.stringify(saved)"
|
||||||
|
:current-field="JSON.stringify(current)"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label id="server-website" title="Your server's website.">
|
<label id="server-website" title="Your server's website.">
|
||||||
<span class="label__title">Website</span>
|
<span class="label__title">Website</span>
|
||||||
@@ -18,7 +23,11 @@
|
|||||||
maxlength="2048"
|
maxlength="2048"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="siteCheck" />
|
<ValidationMessage
|
||||||
|
:check="siteCheck"
|
||||||
|
:project-field="saved.site"
|
||||||
|
:current-field="current.site"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label id="server-store" title="Your server's store page.">
|
<label id="server-store" title="Your server's store page.">
|
||||||
@@ -33,7 +42,11 @@
|
|||||||
maxlength="2048"
|
maxlength="2048"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="storeCheck" />
|
<ValidationMessage
|
||||||
|
:check="storeCheck"
|
||||||
|
:project-field="saved.store"
|
||||||
|
:current-field="current.store"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label
|
<label
|
||||||
@@ -53,7 +66,11 @@
|
|||||||
maxlength="2048"
|
maxlength="2048"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="wikiCheck" />
|
<ValidationMessage
|
||||||
|
:check="wikiCheck"
|
||||||
|
:project-field="saved.wiki"
|
||||||
|
:current-field="current.wiki"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label id="server-discord" title="An invitation link to your Discord server.">
|
<label id="server-discord" title="An invitation link to your Discord server.">
|
||||||
@@ -68,14 +85,23 @@
|
|||||||
maxlength="2048"
|
maxlength="2048"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="discordInviteCheck" />
|
<ValidationMessage
|
||||||
|
:check="discordInviteCheck"
|
||||||
|
:project-field="saved.discord"
|
||||||
|
:current-field="current.discord"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Standard Project Links -->
|
<!-- Standard Project Links -->
|
||||||
<section v-if="!isServerProject" class="universal-card">
|
<section v-if="!isServerProject" class="universal-card">
|
||||||
<h2>External links</h2>
|
<h2>External links</h2>
|
||||||
<ValidationMessage :check="externalLinksValidation" class="mb-4" />
|
<ValidationMessage
|
||||||
|
:check="externalLinksValidation"
|
||||||
|
:project-field="JSON.stringify(saved)"
|
||||||
|
:current-field="JSON.stringify(current)"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label
|
<label
|
||||||
id="project-issue-tracker"
|
id="project-issue-tracker"
|
||||||
@@ -94,7 +120,11 @@
|
|||||||
:maxlength="2048"
|
:maxlength="2048"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="issuesCheck" />
|
<ValidationMessage
|
||||||
|
:check="issuesCheck"
|
||||||
|
:project-field="saved.issues"
|
||||||
|
:current-field="current.issues"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label
|
<label
|
||||||
@@ -114,7 +144,11 @@
|
|||||||
placeholder="Enter a valid URL"
|
placeholder="Enter a valid URL"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="sourceCheck" />
|
<ValidationMessage
|
||||||
|
:check="sourceCheck"
|
||||||
|
:project-field="saved.source"
|
||||||
|
:current-field="current.source"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label
|
<label
|
||||||
@@ -134,7 +168,11 @@
|
|||||||
placeholder="Enter a valid URL"
|
placeholder="Enter a valid URL"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="wikiCheck" />
|
<ValidationMessage
|
||||||
|
:check="wikiCheck"
|
||||||
|
:project-field="saved.wiki"
|
||||||
|
:current-field="current.wiki"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label id="project-discord-invite" title="An invitation link to your Discord server.">
|
<label id="project-discord-invite" title="An invitation link to your Discord server.">
|
||||||
@@ -149,7 +187,11 @@
|
|||||||
placeholder="Enter a valid URL"
|
placeholder="Enter a valid URL"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="discordInviteCheck" />
|
<ValidationMessage
|
||||||
|
:check="discordInviteCheck"
|
||||||
|
:project-field="saved.discord"
|
||||||
|
:current-field="current.discord"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="label">
|
<span class="label">
|
||||||
<span class="label__title">Donation links</span>
|
<span class="label__title">Donation links</span>
|
||||||
|
|||||||
@@ -20,7 +20,12 @@
|
|||||||
:placeholder="formatMessage(messages.selectRegionPlaceholder)"
|
:placeholder="formatMessage(messages.selectRegionPlaceholder)"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="regionValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="regionValidation"
|
||||||
|
:project-field="projectV3?.minecraft_server?.region ?? ''"
|
||||||
|
:current-field="region"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Language -->
|
<!-- Language -->
|
||||||
@@ -43,7 +48,14 @@
|
|||||||
:placeholder="formatMessage(messages.selectLanguagesPlaceholder)"
|
:placeholder="formatMessage(messages.selectLanguagesPlaceholder)"
|
||||||
:disabled="!hasPermission"
|
:disabled="!hasPermission"
|
||||||
/>
|
/>
|
||||||
<ValidationMessage :check="languageValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="languageValidation"
|
||||||
|
:project-field="
|
||||||
|
JSON.stringify([...(projectV3?.minecraft_server?.languages ?? [])].sort())
|
||||||
|
"
|
||||||
|
:current-field="JSON.stringify([...languages].sort())"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Java Address -->
|
<!-- Java Address -->
|
||||||
@@ -129,7 +141,12 @@
|
|||||||
/></template>
|
/></template>
|
||||||
</IntlFormatted>
|
</IntlFormatted>
|
||||||
</div>
|
</div>
|
||||||
<ValidationMessage :check="javaAddressValidation" class="mt-2" />
|
<ValidationMessage
|
||||||
|
:check="javaAddressValidation"
|
||||||
|
:project-field="projectV3?.minecraft_java_server?.address ?? ''"
|
||||||
|
:current-field="javaAddress.trim()"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bedrock Address -->
|
<!-- Bedrock Address -->
|
||||||
|
|||||||
@@ -409,7 +409,11 @@ const toggleFeatured = (tag: string) => {
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ValidationMessage :check="tagValidation" />
|
<ValidationMessage
|
||||||
|
:check="tagValidation"
|
||||||
|
:project-field="JSON.stringify(saved)"
|
||||||
|
:current-field="JSON.stringify(current)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<UnsavedChangesPopup
|
<UnsavedChangesPopup
|
||||||
:original="saved"
|
:original="saved"
|
||||||
|
|||||||
Reference in New Issue
Block a user