You've already forked AstralRinth
forked from didirus/AstralRinth
refactor: migrate to common eslint+prettier configs (#4168)
* refactor: migrate to common eslint+prettier configs * fix: prettier frontend * feat: config changes * fix: lint issues * fix: lint * fix: type imports * fix: cyclical import issue * fix: lockfile * fix: missing dep * fix: switch to tabs * fix: continue switch to tabs * fix: rustfmt parity * fix: moderation lint issue * fix: lint issues * fix: ui intl * fix: lint issues * Revert "fix: rustfmt parity" This reverts commit cb99d2376c321d813d4b7fc7e2a213bb30a54711. * feat: revert last rs
This commit is contained in:
@@ -1,32 +1,32 @@
|
||||
import type { Stage } from '../types/stage'
|
||||
import modpackPermissionsStage from './modpack-permissions-stage'
|
||||
import categories from './stages/categories'
|
||||
import reupload from './stages/reupload'
|
||||
import description from './stages/description'
|
||||
import gallery from './stages/gallery'
|
||||
import license from './stages/license'
|
||||
import links from './stages/links'
|
||||
import reupload from './stages/reupload'
|
||||
import ruleFollowing from './stages/rule-following'
|
||||
import sideTypes from './stages/side-types'
|
||||
import statusAlerts from './stages/status-alerts'
|
||||
import summary from './stages/summary'
|
||||
import titleSlug from './stages/title-slug'
|
||||
import versions from './stages/versions'
|
||||
import license from './stages/license'
|
||||
import undefinedProject from './stages/undefined-project'
|
||||
import statusAlerts from './stages/status-alerts'
|
||||
import versions from './stages/versions'
|
||||
|
||||
export default [
|
||||
titleSlug,
|
||||
summary,
|
||||
description,
|
||||
links,
|
||||
license,
|
||||
categories,
|
||||
sideTypes,
|
||||
gallery,
|
||||
versions,
|
||||
reupload,
|
||||
ruleFollowing,
|
||||
modpackPermissionsStage,
|
||||
statusAlerts,
|
||||
undefinedProject,
|
||||
titleSlug,
|
||||
summary,
|
||||
description,
|
||||
links,
|
||||
license,
|
||||
categories,
|
||||
sideTypes,
|
||||
gallery,
|
||||
versions,
|
||||
reupload,
|
||||
ruleFollowing,
|
||||
modpackPermissionsStage,
|
||||
statusAlerts,
|
||||
undefinedProject,
|
||||
] as ReadonlyArray<Stage>
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import type { KeybindListener } from '../types/keybinds'
|
||||
|
||||
const keybinds: KeybindListener[] = [
|
||||
{
|
||||
id: 'next-stage',
|
||||
keybind: 'ArrowRight',
|
||||
description: 'Go to next stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoNext(),
|
||||
},
|
||||
{
|
||||
id: 'previous-stage',
|
||||
keybind: 'ArrowLeft',
|
||||
description: 'Go to previous stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoBack(),
|
||||
},
|
||||
{
|
||||
id: 'generate-message',
|
||||
keybind: 'Ctrl+Shift+E',
|
||||
description: 'Generate moderation message',
|
||||
action: (ctx) => ctx.actions.tryGenerateMessage(),
|
||||
},
|
||||
{
|
||||
id: 'toggle-collapse',
|
||||
keybind: 'Shift+C',
|
||||
description: 'Toggle collapse/expand',
|
||||
action: (ctx) => ctx.actions.tryToggleCollapse(),
|
||||
},
|
||||
{
|
||||
id: 'reset-progress',
|
||||
keybind: 'Ctrl+Shift+R',
|
||||
description: 'Reset moderation progress',
|
||||
action: (ctx) => ctx.actions.tryResetProgress(),
|
||||
},
|
||||
{
|
||||
id: 'skip-project',
|
||||
keybind: 'Ctrl+Shift+S',
|
||||
description: 'Skip to next project',
|
||||
enabled: (ctx) => ctx.state.futureProjectCount > 0 && !ctx.state.isDone,
|
||||
action: (ctx) => ctx.actions.trySkipProject(),
|
||||
},
|
||||
{
|
||||
id: 'next-stage',
|
||||
keybind: 'ArrowRight',
|
||||
description: 'Go to next stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoNext(),
|
||||
},
|
||||
{
|
||||
id: 'previous-stage',
|
||||
keybind: 'ArrowLeft',
|
||||
description: 'Go to previous stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoBack(),
|
||||
},
|
||||
{
|
||||
id: 'generate-message',
|
||||
keybind: 'Ctrl+Shift+E',
|
||||
description: 'Generate moderation message',
|
||||
action: (ctx) => ctx.actions.tryGenerateMessage(),
|
||||
},
|
||||
{
|
||||
id: 'toggle-collapse',
|
||||
keybind: 'Shift+C',
|
||||
description: 'Toggle collapse/expand',
|
||||
action: (ctx) => ctx.actions.tryToggleCollapse(),
|
||||
},
|
||||
{
|
||||
id: 'reset-progress',
|
||||
keybind: 'Ctrl+Shift+R',
|
||||
description: 'Reset moderation progress',
|
||||
action: (ctx) => ctx.actions.tryResetProgress(),
|
||||
},
|
||||
{
|
||||
id: 'skip-project',
|
||||
keybind: 'Ctrl+Shift+S',
|
||||
description: 'Skip to next project',
|
||||
enabled: (ctx) => ctx.state.futureProjectCount > 0 && !ctx.state.isDone,
|
||||
action: (ctx) => ctx.actions.trySkipProject(),
|
||||
},
|
||||
]
|
||||
|
||||
export default keybinds
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import type { ModerationModpackPermissionApprovalType, Project } from '@modrinth/utils'
|
||||
import type { Stage } from '../types/stage'
|
||||
import { PackageOpenIcon } from '@modrinth/assets'
|
||||
import type { ModerationModpackPermissionApprovalType, Project } from '@modrinth/utils'
|
||||
|
||||
import type { Stage } from '../types/stage'
|
||||
|
||||
export default {
|
||||
id: 'modpack-permissions',
|
||||
title: 'Modpack Permissions',
|
||||
icon: PackageOpenIcon,
|
||||
// Replace me please.
|
||||
guidance_url:
|
||||
'https://www.notion.so/Content-Moderation-Cheat-Sheets-22d5ee711bf081a4920ef08879fe6bf5?source=copy_link#22d5ee711bf08116bd8bc1186f357062',
|
||||
shouldShow: (project: Project) => project.project_type === 'modpack',
|
||||
actions: [
|
||||
{
|
||||
id: 'button',
|
||||
type: 'button',
|
||||
label: 'This dummy button must be present or the stage will not appear.',
|
||||
},
|
||||
],
|
||||
id: 'modpack-permissions',
|
||||
title: 'Modpack Permissions',
|
||||
icon: PackageOpenIcon,
|
||||
// Replace me please.
|
||||
guidance_url:
|
||||
'https://www.notion.so/Content-Moderation-Cheat-Sheets-22d5ee711bf081a4920ef08879fe6bf5?source=copy_link#22d5ee711bf08116bd8bc1186f357062',
|
||||
shouldShow: (project: Project) => project.project_type === 'modpack',
|
||||
actions: [
|
||||
{
|
||||
id: 'button',
|
||||
type: 'button',
|
||||
label: 'This dummy button must be present or the stage will not appear.',
|
||||
},
|
||||
],
|
||||
} as Stage
|
||||
|
||||
export const finalPermissionMessages: Record<
|
||||
ModerationModpackPermissionApprovalType['id'],
|
||||
string | undefined
|
||||
ModerationModpackPermissionApprovalType['id'],
|
||||
string | undefined
|
||||
> = {
|
||||
yes: undefined,
|
||||
'with-attribution-and-source': undefined,
|
||||
'with-attribution': `The following content has attribution requirements, meaning that you must link back to the page where you originally found this content in your Modpack's description or version changelog (e.g. linking a mod's CurseForge page if you got it from CurseForge):`,
|
||||
no: 'The following content is not allowed in Modrinth modpacks due to licensing restrictions. Please contact the author(s) directly for permission or remove the content from your modpack:',
|
||||
'permanent-no': `The following content is not allowed in Modrinth modpacks, regardless of permission obtained. This may be because it breaks Modrinth's content rules or because the authors, upon being contacted for permission, have declined. Please remove the content from your modpack:`,
|
||||
unidentified: `The following content could not be identified. Please provide proof of its origin along with proof that you have permission to include it:`,
|
||||
yes: undefined,
|
||||
'with-attribution-and-source': undefined,
|
||||
'with-attribution': `The following content has attribution requirements, meaning that you must link back to the page where you originally found this content in your Modpack's description or version changelog (e.g. linking a mod's CurseForge page if you got it from CurseForge):`,
|
||||
no: 'The following content is not allowed in Modrinth modpacks due to licensing restrictions. Please contact the author(s) directly for permission or remove the content from your modpack:',
|
||||
'permanent-no': `The following content is not allowed in Modrinth modpacks, regardless of permission obtained. This may be because it breaks Modrinth's content rules or because the authors, upon being contacted for permission, have declined. Please remove the content from your modpack:`,
|
||||
unidentified: `The following content could not be identified. Please provide proof of its origin along with proof that you have permission to include it:`,
|
||||
}
|
||||
|
||||
@@ -1,292 +1,293 @@
|
||||
import type { Nag, NagContext } from '../../types/nags'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
import { useVIntl, defineMessage } from '@vintl/vintl'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
|
||||
import type { Nag, NagContext } from '../../types/nags'
|
||||
|
||||
export const coreNags: Nag[] = [
|
||||
{
|
||||
id: 'moderator-feedback',
|
||||
title: defineMessage({
|
||||
id: 'nags.moderator-feedback.title',
|
||||
defaultMessage: 'Review moderator feedback',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.moderator-feedback.description',
|
||||
defaultMessage:
|
||||
'Review and address all concerns from the moderation team before resubmitting.',
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) =>
|
||||
context.tags.rejectedStatuses.includes(context.project.status),
|
||||
link: {
|
||||
path: 'moderation',
|
||||
title: defineMessage({
|
||||
id: 'nags.moderation.title',
|
||||
defaultMessage: 'Visit moderation thread',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-moderation',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'upload-version',
|
||||
title: defineMessage({
|
||||
id: 'nags.upload-version.title',
|
||||
defaultMessage: 'Upload a version',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.upload-version.description',
|
||||
defaultMessage: 'At least one version is required for a project to be submitted for review.',
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => context.versions.length < 1,
|
||||
link: {
|
||||
path: 'versions',
|
||||
title: defineMessage({
|
||||
id: 'nags.versions.title',
|
||||
defaultMessage: 'Visit versions page',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-versions',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'add-description',
|
||||
title: defineMessage({
|
||||
id: 'nags.add-description.title',
|
||||
defaultMessage: 'Add a description',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.add-description.description',
|
||||
defaultMessage:
|
||||
"A description that clearly describes the project's purpose and function is required.",
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => context.project.body === '',
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.description.title',
|
||||
defaultMessage: 'Visit description settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'add-icon',
|
||||
title: defineMessage({
|
||||
id: 'nags.add-icon.title',
|
||||
defaultMessage: 'Add an icon',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.add-icon.description',
|
||||
defaultMessage:
|
||||
'Adding a unique, relevant, and engaging icon makes your project identifiable and helps it stand out.',
|
||||
}),
|
||||
status: 'suggestion',
|
||||
shouldShow: (context: NagContext) => !context.project.icon_url,
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.title',
|
||||
defaultMessage: 'Visit general settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'upload-gallery-image',
|
||||
title: defineMessage({
|
||||
id: 'nags.upload-gallery-image.title',
|
||||
defaultMessage: 'Upload a gallery image',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const projectType = formatProjectType(context.project.project_type).toLowerCase()
|
||||
let msg = ''
|
||||
if (context.project.project_type === 'resourcepack') {
|
||||
msg =
|
||||
', except for audio or localization packs. If this describes your pack, please select the appropriate tag'
|
||||
}
|
||||
const resourcepackMessage = msg
|
||||
{
|
||||
id: 'moderator-feedback',
|
||||
title: defineMessage({
|
||||
id: 'nags.moderator-feedback.title',
|
||||
defaultMessage: 'Review moderator feedback',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.moderator-feedback.description',
|
||||
defaultMessage:
|
||||
'Review and address all concerns from the moderation team before resubmitting.',
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) =>
|
||||
context.tags.rejectedStatuses.includes(context.project.status),
|
||||
link: {
|
||||
path: 'moderation',
|
||||
title: defineMessage({
|
||||
id: 'nags.moderation.title',
|
||||
defaultMessage: 'Visit moderation thread',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-moderation',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'upload-version',
|
||||
title: defineMessage({
|
||||
id: 'nags.upload-version.title',
|
||||
defaultMessage: 'Upload a version',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.upload-version.description',
|
||||
defaultMessage: 'At least one version is required for a project to be submitted for review.',
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => context.versions.length < 1,
|
||||
link: {
|
||||
path: 'versions',
|
||||
title: defineMessage({
|
||||
id: 'nags.versions.title',
|
||||
defaultMessage: 'Visit versions page',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-versions',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'add-description',
|
||||
title: defineMessage({
|
||||
id: 'nags.add-description.title',
|
||||
defaultMessage: 'Add a description',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.add-description.description',
|
||||
defaultMessage:
|
||||
"A description that clearly describes the project's purpose and function is required.",
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => context.project.body === '',
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.description.title',
|
||||
defaultMessage: 'Visit description settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'add-icon',
|
||||
title: defineMessage({
|
||||
id: 'nags.add-icon.title',
|
||||
defaultMessage: 'Add an icon',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.add-icon.description',
|
||||
defaultMessage:
|
||||
'Adding a unique, relevant, and engaging icon makes your project identifiable and helps it stand out.',
|
||||
}),
|
||||
status: 'suggestion',
|
||||
shouldShow: (context: NagContext) => !context.project.icon_url,
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.title',
|
||||
defaultMessage: 'Visit general settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'upload-gallery-image',
|
||||
title: defineMessage({
|
||||
id: 'nags.upload-gallery-image.title',
|
||||
defaultMessage: 'Upload a gallery image',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const projectType = formatProjectType(context.project.project_type).toLowerCase()
|
||||
let msg = ''
|
||||
if (context.project.project_type === 'resourcepack') {
|
||||
msg =
|
||||
', except for audio or localization packs. If this describes your pack, please select the appropriate tag'
|
||||
}
|
||||
const resourcepackMessage = msg
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.upload-gallery-image.description',
|
||||
defaultMessage:
|
||||
'At least one gallery image is required to showcase the content of your {type}{resourcepackMessage}.',
|
||||
}),
|
||||
{
|
||||
type: projectType,
|
||||
resourcepackMessage: resourcepackMessage,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
return (
|
||||
(context.project.project_type === 'resourcepack' ||
|
||||
context.project.project_type === 'shader') &&
|
||||
(!context.project.gallery || context.project.gallery?.length === 0) &&
|
||||
!(
|
||||
context.project.categories.includes('audio') ||
|
||||
context.project.additional_categories.includes('audio') ||
|
||||
context.project.categories.includes('locale') ||
|
||||
context.project.additional_categories.includes('locale')
|
||||
)
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'gallery',
|
||||
title: defineMessage({
|
||||
id: 'nags.gallery.title',
|
||||
defaultMessage: 'Visit gallery page',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-gallery',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'feature-gallery-image',
|
||||
title: defineMessage({
|
||||
id: 'nags.feature-gallery-image.title',
|
||||
defaultMessage: 'Feature a gallery image',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.feature-gallery-image.description',
|
||||
defaultMessage:
|
||||
'The featured gallery image is often how your project makes its first impression.',
|
||||
}),
|
||||
status: 'suggestion',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const featuredGalleryImage = context.project.gallery?.find((img) => img.featured)
|
||||
return context.project?.gallery?.length === 0 || !featuredGalleryImage
|
||||
},
|
||||
link: {
|
||||
path: 'gallery',
|
||||
title: defineMessage({
|
||||
id: 'nags.gallery.title',
|
||||
defaultMessage: 'Visit gallery page',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-gallery',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'select-tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.select-tags.title',
|
||||
defaultMessage: 'Select tags',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.select-tags.description',
|
||||
defaultMessage:
|
||||
'Select the tags that correctly apply to your project to help the right users find it.',
|
||||
}),
|
||||
status: 'suggestion',
|
||||
shouldShow: (context: NagContext) =>
|
||||
context.project.versions.length > 0 && context.project.categories.length < 1,
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.tags.title',
|
||||
defaultMessage: 'Visit tag settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'add-links',
|
||||
title: defineMessage({
|
||||
id: 'nags.add-links.title',
|
||||
defaultMessage: 'Add external links',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.add-links.description',
|
||||
defaultMessage:
|
||||
'Add any relevant links targeted outside of Modrinth, such as source code, an issue tracker, or a Discord invite.',
|
||||
}),
|
||||
status: 'suggestion',
|
||||
shouldShow: (context: NagContext) =>
|
||||
!(
|
||||
context.project.issues_url ||
|
||||
context.project.source_url ||
|
||||
context.project.wiki_url ||
|
||||
context.project.discord_url ||
|
||||
context.project.donation_urls.length > 0
|
||||
),
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.links.title',
|
||||
defaultMessage: 'Visit links settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'select-environments',
|
||||
title: defineMessage({
|
||||
id: 'nags.select-environments.title',
|
||||
defaultMessage: 'Select supported environments',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.upload-gallery-image.description',
|
||||
defaultMessage:
|
||||
'At least one gallery image is required to showcase the content of your {type}{resourcepackMessage}.',
|
||||
}),
|
||||
{
|
||||
type: projectType,
|
||||
resourcepackMessage: resourcepackMessage,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
return (
|
||||
(context.project.project_type === 'resourcepack' ||
|
||||
context.project.project_type === 'shader') &&
|
||||
(!context.project.gallery || context.project.gallery?.length === 0) &&
|
||||
!(
|
||||
context.project.categories.includes('audio') ||
|
||||
context.project.additional_categories.includes('audio') ||
|
||||
context.project.categories.includes('locale') ||
|
||||
context.project.additional_categories.includes('locale')
|
||||
)
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'gallery',
|
||||
title: defineMessage({
|
||||
id: 'nags.gallery.title',
|
||||
defaultMessage: 'Visit gallery page',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-gallery',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'feature-gallery-image',
|
||||
title: defineMessage({
|
||||
id: 'nags.feature-gallery-image.title',
|
||||
defaultMessage: 'Feature a gallery image',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.feature-gallery-image.description',
|
||||
defaultMessage:
|
||||
'The featured gallery image is often how your project makes its first impression.',
|
||||
}),
|
||||
status: 'suggestion',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const featuredGalleryImage = context.project.gallery?.find((img) => img.featured)
|
||||
return context.project?.gallery?.length === 0 || !featuredGalleryImage
|
||||
},
|
||||
link: {
|
||||
path: 'gallery',
|
||||
title: defineMessage({
|
||||
id: 'nags.gallery.title',
|
||||
defaultMessage: 'Visit gallery page',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-gallery',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'select-tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.select-tags.title',
|
||||
defaultMessage: 'Select tags',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.select-tags.description',
|
||||
defaultMessage:
|
||||
'Select the tags that correctly apply to your project to help the right users find it.',
|
||||
}),
|
||||
status: 'suggestion',
|
||||
shouldShow: (context: NagContext) =>
|
||||
context.project.versions.length > 0 && context.project.categories.length < 1,
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.tags.title',
|
||||
defaultMessage: 'Visit tag settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'add-links',
|
||||
title: defineMessage({
|
||||
id: 'nags.add-links.title',
|
||||
defaultMessage: 'Add external links',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.add-links.description',
|
||||
defaultMessage:
|
||||
'Add any relevant links targeted outside of Modrinth, such as source code, an issue tracker, or a Discord invite.',
|
||||
}),
|
||||
status: 'suggestion',
|
||||
shouldShow: (context: NagContext) =>
|
||||
!(
|
||||
context.project.issues_url ||
|
||||
context.project.source_url ||
|
||||
context.project.wiki_url ||
|
||||
context.project.discord_url ||
|
||||
context.project.donation_urls.length > 0
|
||||
),
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.links.title',
|
||||
defaultMessage: 'Visit links settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'select-environments',
|
||||
title: defineMessage({
|
||||
id: 'nags.select-environments.title',
|
||||
defaultMessage: 'Select supported environments',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.select-environments.description',
|
||||
defaultMessage: `Select if the {projectType} functions on the client-side and/or server-side.`,
|
||||
}),
|
||||
{
|
||||
projectType: formatProjectType(context.project.project_type).toLowerCase(),
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const excludedTypes = ['resourcepack', 'plugin', 'shader', 'datapack']
|
||||
return (
|
||||
context.project.versions.length > 0 &&
|
||||
!excludedTypes.includes(context.project.project_type) &&
|
||||
(context.project.client_side === 'unknown' ||
|
||||
context.project.server_side === 'unknown' ||
|
||||
(context.project.client_side === 'unsupported' &&
|
||||
context.project.server_side === 'unsupported'))
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.environments.title',
|
||||
defaultMessage: 'Visit general settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'select-license',
|
||||
title: defineMessage({
|
||||
id: 'nags.select-license.title',
|
||||
defaultMessage: 'Select a license',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.select-environments.description',
|
||||
defaultMessage: `Select if the {projectType} functions on the client-side and/or server-side.`,
|
||||
}),
|
||||
{
|
||||
projectType: formatProjectType(context.project.project_type).toLowerCase(),
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const excludedTypes = ['resourcepack', 'plugin', 'shader', 'datapack']
|
||||
return (
|
||||
context.project.versions.length > 0 &&
|
||||
!excludedTypes.includes(context.project.project_type) &&
|
||||
(context.project.client_side === 'unknown' ||
|
||||
context.project.server_side === 'unknown' ||
|
||||
(context.project.client_side === 'unsupported' &&
|
||||
context.project.server_side === 'unsupported'))
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.environments.title',
|
||||
defaultMessage: 'Visit general settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'select-license',
|
||||
title: defineMessage({
|
||||
id: 'nags.select-license.title',
|
||||
defaultMessage: 'Select a license',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.select-license.description',
|
||||
defaultMessage: 'Select the license your {projectType} is distributed under.',
|
||||
}),
|
||||
{
|
||||
projectType: formatProjectType(context.project.project_type).toLowerCase(),
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => context.project.license.id === 'LicenseRef-Unknown',
|
||||
link: {
|
||||
path: 'settings/license',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.license.title',
|
||||
defaultMessage: 'Visit license settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-license',
|
||||
},
|
||||
},
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.select-license.description',
|
||||
defaultMessage: 'Select the license your {projectType} is distributed under.',
|
||||
}),
|
||||
{
|
||||
projectType: formatProjectType(context.project.project_type).toLowerCase(),
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => context.project.license.id === 'LicenseRef-Unknown',
|
||||
link: {
|
||||
path: 'settings/license',
|
||||
title: defineMessage({
|
||||
id: 'nags.settings.license.title',
|
||||
defaultMessage: 'Visit license settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-license',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { renderHighlightedString } from '@modrinth/utils'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
|
||||
import type { Nag, NagContext } from '../../types/nags'
|
||||
import { useVIntl, defineMessage } from '@vintl/vintl'
|
||||
|
||||
export const MIN_DESCRIPTION_CHARS = 200
|
||||
export const MAX_HEADER_LENGTH = 80
|
||||
@@ -8,388 +9,388 @@ export const MIN_SUMMARY_CHARS = 30
|
||||
export const MIN_CHARS_PER_IMAGE = 60
|
||||
|
||||
export function analyzeHeaderLength(markdown: string): {
|
||||
hasLongHeaders: boolean
|
||||
longHeaders: string[]
|
||||
hasLongHeaders: boolean
|
||||
longHeaders: string[]
|
||||
} {
|
||||
if (!markdown) return { hasLongHeaders: false, longHeaders: [] }
|
||||
if (!markdown) return { hasLongHeaders: false, longHeaders: [] }
|
||||
|
||||
const withoutCodeBlocks = markdown.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
|
||||
const withoutCodeBlocks = markdown.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
|
||||
|
||||
const headerRegex = /^(#{1,3})\s+(.+)$/gm
|
||||
const headers = [...withoutCodeBlocks.matchAll(headerRegex)]
|
||||
const headerRegex = /^(#{1,3})\s+(.+)$/gm
|
||||
const headers = [...withoutCodeBlocks.matchAll(headerRegex)]
|
||||
|
||||
const longHeaders: string[] = []
|
||||
const longHeaders: string[] = []
|
||||
|
||||
headers.forEach((match) => {
|
||||
const headerText = match[2].trim()
|
||||
const sentenceEnders = /[.!?]+/g
|
||||
const sentences = headerText.split(sentenceEnders).filter((s) => s.trim().length > 0)
|
||||
headers.forEach((match) => {
|
||||
const headerText = match[2].trim()
|
||||
const sentenceEnders = /[.!?]+/g
|
||||
const sentences = headerText.split(sentenceEnders).filter((s) => s.trim().length > 0)
|
||||
|
||||
const isVeryLong = headerText.length > MAX_HEADER_LENGTH
|
||||
const hasMultipleSentences = sentences.length > 1
|
||||
const isVeryLong = headerText.length > MAX_HEADER_LENGTH
|
||||
const hasMultipleSentences = sentences.length > 1
|
||||
|
||||
if (isVeryLong || hasMultipleSentences) {
|
||||
longHeaders.push(headerText)
|
||||
}
|
||||
})
|
||||
if (isVeryLong || hasMultipleSentences) {
|
||||
longHeaders.push(headerText)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
hasLongHeaders: longHeaders.length > 0,
|
||||
longHeaders,
|
||||
}
|
||||
return {
|
||||
hasLongHeaders: longHeaders.length > 0,
|
||||
longHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
export function analyzeImageContent(markdown: string): {
|
||||
imageHeavy: boolean
|
||||
hasEmptyAltText: boolean
|
||||
imageHeavy: boolean
|
||||
hasEmptyAltText: boolean
|
||||
} {
|
||||
if (!markdown) return { imageHeavy: false, hasEmptyAltText: false }
|
||||
if (!markdown) return { imageHeavy: false, hasEmptyAltText: false }
|
||||
|
||||
const withoutCodeBlocks = markdown.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
|
||||
const withoutCodeBlocks = markdown.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
|
||||
|
||||
const imageRegex = /!\[([^\]]*)\]\([^)]+\)/g
|
||||
const images = [...withoutCodeBlocks.matchAll(imageRegex)]
|
||||
const imageRegex = /!\[([^\]]*)\]\([^)]+\)/g
|
||||
const images = [...withoutCodeBlocks.matchAll(imageRegex)]
|
||||
|
||||
const htmlImageRegex = /<img[^>]*>/gi
|
||||
const htmlImages = [...withoutCodeBlocks.matchAll(htmlImageRegex)]
|
||||
const htmlImageRegex = /<img[^>]*>/gi
|
||||
const htmlImages = [...withoutCodeBlocks.matchAll(htmlImageRegex)]
|
||||
|
||||
const totalImages = images.length + htmlImages.length
|
||||
if (totalImages === 0) return { imageHeavy: false, hasEmptyAltText: false }
|
||||
const totalImages = images.length + htmlImages.length
|
||||
if (totalImages === 0) return { imageHeavy: false, hasEmptyAltText: false }
|
||||
|
||||
const textLength = countText(withoutCodeBlocks)
|
||||
const recommendedTextLength = MIN_CHARS_PER_IMAGE * totalImages
|
||||
const imageHeavy =
|
||||
recommendedTextLength > MIN_DESCRIPTION_CHARS && textLength < recommendedTextLength
|
||||
const textLength = countText(withoutCodeBlocks)
|
||||
const recommendedTextLength = MIN_CHARS_PER_IMAGE * totalImages
|
||||
const imageHeavy =
|
||||
recommendedTextLength > MIN_DESCRIPTION_CHARS && textLength < recommendedTextLength
|
||||
|
||||
const hasEmptyAltText =
|
||||
images.some((match) => !match[1]?.trim()) ||
|
||||
htmlImages.some((match) => {
|
||||
const altMatch = match[0].match(/alt\s*=\s*["']([^"']*)["']/i)
|
||||
return !altMatch || !altMatch[1]?.trim()
|
||||
})
|
||||
const hasEmptyAltText =
|
||||
images.some((match) => !match[1]?.trim()) ||
|
||||
htmlImages.some((match) => {
|
||||
const altMatch = match[0].match(/alt\s*=\s*["']([^"']*)["']/i)
|
||||
return !altMatch || !altMatch[1]?.trim()
|
||||
})
|
||||
|
||||
return { imageHeavy, hasEmptyAltText }
|
||||
return { imageHeavy, hasEmptyAltText }
|
||||
}
|
||||
|
||||
export function countText(markdown: string): number {
|
||||
if (!markdown) return 0
|
||||
if (!markdown) return 0
|
||||
|
||||
const fallback = (md: string): number => {
|
||||
const withoutCode = md.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
|
||||
const withoutImagesAndLinks = withoutCode
|
||||
.replace(/!\[[^\]]*]\([^)]+\)/g, ' ')
|
||||
.replace(/\[[^\]]*]\([^)]+\)/g, ' ')
|
||||
const withoutHtml = withoutImagesAndLinks.replace(/<[^>]+>/g, ' ')
|
||||
const withoutMdSyntax = withoutHtml
|
||||
.replace(/^>{1}\s?.*$/gm, ' ')
|
||||
.replace(/^#{1,6}\s+/gm, ' ')
|
||||
.replace(/[*_~`>-]/g, ' ')
|
||||
.replace(/\|/g, ' ')
|
||||
return withoutMdSyntax.replace(/\s+/g, ' ').trim().length
|
||||
}
|
||||
const fallback = (md: string): number => {
|
||||
const withoutCode = md.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
|
||||
const withoutImagesAndLinks = withoutCode
|
||||
.replace(/!\[[^\]]*]\([^)]+\)/g, ' ')
|
||||
.replace(/\[[^\]]*]\([^)]+\)/g, ' ')
|
||||
const withoutHtml = withoutImagesAndLinks.replace(/<[^>]+>/g, ' ')
|
||||
const withoutMdSyntax = withoutHtml
|
||||
.replace(/^>{1}\s?.*$/gm, ' ')
|
||||
.replace(/^#{1,6}\s+/gm, ' ')
|
||||
.replace(/[*_~`>-]/g, ' ')
|
||||
.replace(/\|/g, ' ')
|
||||
return withoutMdSyntax.replace(/\s+/g, ' ').trim().length
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined' || typeof globalThis.DOMParser === 'undefined') {
|
||||
console.warn(`[Moderation] SSR: no window/DOMParser, falling back for countText`)
|
||||
return fallback(markdown)
|
||||
}
|
||||
if (typeof window === 'undefined' || typeof globalThis.DOMParser === 'undefined') {
|
||||
console.warn(`[Moderation] SSR: no window/DOMParser, falling back for countText`)
|
||||
return fallback(markdown)
|
||||
}
|
||||
|
||||
try {
|
||||
const htmlString = renderHighlightedString(markdown)
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(htmlString, 'text/html')
|
||||
const walker = doc.createTreeWalker(doc.body || doc, NodeFilter.SHOW_TEXT)
|
||||
try {
|
||||
const htmlString = renderHighlightedString(markdown)
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(htmlString, 'text/html')
|
||||
const walker = doc.createTreeWalker(doc.body || doc, NodeFilter.SHOW_TEXT)
|
||||
|
||||
const textList: string[] = []
|
||||
let node = walker.nextNode()
|
||||
while (node) {
|
||||
if (node.textContent) textList.push(node.textContent)
|
||||
node = walker.nextNode()
|
||||
}
|
||||
return textList.join(' ').replace(/\s+/g, ' ').trim().length
|
||||
} catch {
|
||||
return fallback(markdown)
|
||||
}
|
||||
const textList: string[] = []
|
||||
let node = walker.nextNode()
|
||||
while (node) {
|
||||
if (node.textContent) textList.push(node.textContent)
|
||||
node = walker.nextNode()
|
||||
}
|
||||
return textList.join(' ').replace(/\s+/g, ' ').trim().length
|
||||
} catch {
|
||||
return fallback(markdown)
|
||||
}
|
||||
}
|
||||
|
||||
export const descriptionNags: Nag[] = [
|
||||
{
|
||||
id: 'description-too-short',
|
||||
title: defineMessage({
|
||||
id: 'nags.description-too-short.title',
|
||||
defaultMessage: 'Expand the description',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const readableLength = countText(context.project.body || '')
|
||||
{
|
||||
id: 'description-too-short',
|
||||
title: defineMessage({
|
||||
id: 'nags.description-too-short.title',
|
||||
defaultMessage: 'Expand the description',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const readableLength = countText(context.project.body || '')
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.description-too-short.description',
|
||||
defaultMessage:
|
||||
'Your description is {length} readable characters. At least {minChars} characters is recommended to create a clear and informative description.',
|
||||
}),
|
||||
{
|
||||
length: readableLength,
|
||||
minChars: MIN_DESCRIPTION_CHARS,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const readableLength = countText(context.project.body || '')
|
||||
return readableLength < MIN_DESCRIPTION_CHARS && readableLength > 0
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-description.title',
|
||||
defaultMessage: 'Edit description',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'long-headers',
|
||||
title: defineMessage({
|
||||
id: 'nags.long-headers.title',
|
||||
defaultMessage: 'Shorten headers',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const { longHeaders } = analyzeHeaderLength(context.project.body || '')
|
||||
const count = longHeaders.length
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.description-too-short.description',
|
||||
defaultMessage:
|
||||
'Your description is {length} readable characters. At least {minChars} characters is recommended to create a clear and informative description.',
|
||||
}),
|
||||
{
|
||||
length: readableLength,
|
||||
minChars: MIN_DESCRIPTION_CHARS,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const readableLength = countText(context.project.body || '')
|
||||
return readableLength < MIN_DESCRIPTION_CHARS && readableLength > 0
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-description.title',
|
||||
defaultMessage: 'Edit description',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'long-headers',
|
||||
title: defineMessage({
|
||||
id: 'nags.long-headers.title',
|
||||
defaultMessage: 'Shorten headers',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const { longHeaders } = analyzeHeaderLength(context.project.body || '')
|
||||
const count = longHeaders.length
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.long-headers.description',
|
||||
defaultMessage:
|
||||
'{count, plural, one {# header} other {# headers}} in your description {count, plural, one {is} other {are}} too long. Headers should be concise and act as section titles, not full sentences.',
|
||||
}),
|
||||
{
|
||||
count,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const { hasLongHeaders } = analyzeHeaderLength(context.project.body || '')
|
||||
return hasLongHeaders
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-description.title',
|
||||
defaultMessage: 'Edit description',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'summary-too-short',
|
||||
title: defineMessage({
|
||||
id: 'nags.summary-too-short.title',
|
||||
defaultMessage: 'Expand the summary',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.long-headers.description',
|
||||
defaultMessage:
|
||||
'{count, plural, one {# header} other {# headers}} in your description {count, plural, one {is} other {are}} too long. Headers should be concise and act as section titles, not full sentences.',
|
||||
}),
|
||||
{
|
||||
count,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const { hasLongHeaders } = analyzeHeaderLength(context.project.body || '')
|
||||
return hasLongHeaders
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-description.title',
|
||||
defaultMessage: 'Edit description',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'summary-too-short',
|
||||
title: defineMessage({
|
||||
id: 'nags.summary-too-short.title',
|
||||
defaultMessage: 'Expand the summary',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.summary-too-short.description',
|
||||
defaultMessage:
|
||||
'Your summary is {length} characters. At least {minChars} characters is recommended to create an informative and enticing summary.',
|
||||
}),
|
||||
{
|
||||
length: context.project.description?.length || 0,
|
||||
minChars: MIN_SUMMARY_CHARS,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const summaryLength = context.project.description?.trim()?.length || 0
|
||||
return summaryLength < MIN_SUMMARY_CHARS && summaryLength !== 0
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-summary.title',
|
||||
defaultMessage: 'Edit summary',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'summary-special-formatting',
|
||||
title: defineMessage({
|
||||
id: 'nags.summary-special-formatting.title',
|
||||
defaultMessage: 'Clear up the summary',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.summary-special-formatting.description',
|
||||
defaultMessage: `Your summary should not contain formatting, line breaks, special characters, or links, since the summary will only display plain text.`,
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const summary = context.project.description?.trim() || ''
|
||||
return Boolean(
|
||||
summary.match(/https:\/\//g) ||
|
||||
summary.match(/http:\/\//g) ||
|
||||
summary.match(/# .*/g) ||
|
||||
summary.match(/---/g) ||
|
||||
summary.match(/\n/g) ||
|
||||
summary.match(/\[.*\]\(.*\)/g) ||
|
||||
summary.match(/!\[.*\]/g) ||
|
||||
summary.match(/`.*`/g) ||
|
||||
summary.match(/\*.*\*/g) ||
|
||||
summary.match(/_.*_/g) ||
|
||||
summary.match(/~~.*~~/g) ||
|
||||
summary.match(/```/g) ||
|
||||
summary.match(/> /g),
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-summary.title',
|
||||
defaultMessage: 'Edit summary',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'minecraft-title-clause',
|
||||
title: defineMessage({
|
||||
id: 'nags.minecraft-title-clause.title',
|
||||
defaultMessage: 'Avoid brand infringement',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.minecraft-title-clause.description',
|
||||
defaultMessage: `Projects must not use Minecraft's branding or include "Minecraft" as a significant part of the name.`,
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const title = context.project.title?.toLowerCase() || ''
|
||||
const wordsInTitle = title.split(' ').filter((word) => word.length > 0)
|
||||
return title.includes('minecraft') && title.length > 0 && wordsInTitle.length <= 3
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-title.title',
|
||||
defaultMessage: 'Edit title',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'title-contains-technical-info',
|
||||
title: defineMessage({
|
||||
id: 'nags.title-contains-technical-info.title',
|
||||
defaultMessage: 'Clean up the name',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.title-contains-technical-info.description',
|
||||
defaultMessage:
|
||||
"Keeping your project's Name clean and makes it memorable easier to find. Version and loader information is automatically displayed alongside your project.",
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const title = context.project.title?.toLowerCase() || ''
|
||||
if (!title) return false
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.summary-too-short.description',
|
||||
defaultMessage:
|
||||
'Your summary is {length} characters. At least {minChars} characters is recommended to create an informative and enticing summary.',
|
||||
}),
|
||||
{
|
||||
length: context.project.description?.length || 0,
|
||||
minChars: MIN_SUMMARY_CHARS,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const summaryLength = context.project.description?.trim()?.length || 0
|
||||
return summaryLength < MIN_SUMMARY_CHARS && summaryLength !== 0
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-summary.title',
|
||||
defaultMessage: 'Edit summary',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'summary-special-formatting',
|
||||
title: defineMessage({
|
||||
id: 'nags.summary-special-formatting.title',
|
||||
defaultMessage: 'Clear up the summary',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.summary-special-formatting.description',
|
||||
defaultMessage: `Your summary should not contain formatting, line breaks, special characters, or links, since the summary will only display plain text.`,
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const summary = context.project.description?.trim() || ''
|
||||
return Boolean(
|
||||
summary.match(/https:\/\//g) ||
|
||||
summary.match(/http:\/\//g) ||
|
||||
summary.match(/# .*/g) ||
|
||||
summary.match(/---/g) ||
|
||||
summary.match(/\n/g) ||
|
||||
summary.match(/\[.*\]\(.*\)/g) ||
|
||||
summary.match(/!\[.*\]/g) ||
|
||||
summary.match(/`.*`/g) ||
|
||||
summary.match(/\*.*\*/g) ||
|
||||
summary.match(/_.*_/g) ||
|
||||
summary.match(/~~.*~~/g) ||
|
||||
summary.match(/```/g) ||
|
||||
summary.match(/> /g),
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-summary.title',
|
||||
defaultMessage: 'Edit summary',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'minecraft-title-clause',
|
||||
title: defineMessage({
|
||||
id: 'nags.minecraft-title-clause.title',
|
||||
defaultMessage: 'Avoid brand infringement',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.minecraft-title-clause.description',
|
||||
defaultMessage: `Projects must not use Minecraft's branding or include "Minecraft" as a significant part of the name.`,
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const title = context.project.title?.toLowerCase() || ''
|
||||
const wordsInTitle = title.split(' ').filter((word) => word.length > 0)
|
||||
return title.includes('minecraft') && title.length > 0 && wordsInTitle.length <= 3
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-title.title',
|
||||
defaultMessage: 'Edit title',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'title-contains-technical-info',
|
||||
title: defineMessage({
|
||||
id: 'nags.title-contains-technical-info.title',
|
||||
defaultMessage: 'Clean up the name',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.title-contains-technical-info.description',
|
||||
defaultMessage:
|
||||
"Keeping your project's Name clean and makes it memorable easier to find. Version and loader information is automatically displayed alongside your project.",
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const title = context.project.title?.toLowerCase() || ''
|
||||
if (!title) return false
|
||||
|
||||
const loaderNames =
|
||||
context.tags.loaders?.map((loader: { name: string }) => loader.name?.toLowerCase()) || []
|
||||
const hasLoader = loaderNames.some((loader) => loader && title.includes(loader.toLowerCase()))
|
||||
const versionPatterns = [/\b1\.\d+(\.\d+)?\b/]
|
||||
const hasVersionPattern = versionPatterns.some((pattern) => pattern.test(title))
|
||||
const loaderNames =
|
||||
context.tags.loaders?.map((loader: { name: string }) => loader.name?.toLowerCase()) || []
|
||||
const hasLoader = loaderNames.some((loader) => loader && title.includes(loader.toLowerCase()))
|
||||
const versionPatterns = [/\b1\.\d+(\.\d+)?\b/]
|
||||
const hasVersionPattern = versionPatterns.some((pattern) => pattern.test(title))
|
||||
|
||||
return hasLoader || hasVersionPattern
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-title.title',
|
||||
defaultMessage: 'Edit title',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'summary-same-as-title',
|
||||
title: defineMessage({
|
||||
id: 'nags.summary-same-as-title.title',
|
||||
defaultMessage: 'Make the summary unique',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.summary-same-as-title.description',
|
||||
defaultMessage:
|
||||
"Your summary can not be the same as your project's Name. It's important to create an informative and enticing Summary.",
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const title = context.project.title?.trim() || ''
|
||||
const summary = context.project.description?.trim() || ''
|
||||
return title === summary && title.length > 0 && summary.length > 0
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-summary.title',
|
||||
defaultMessage: 'Edit summary',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Don't like this one, is this needed?
|
||||
id: 'image-heavy-description',
|
||||
title: defineMessage({
|
||||
id: 'nags.image-heavy-description.title',
|
||||
defaultMessage: 'Ensure accessibility',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.image-heavy-description.description',
|
||||
defaultMessage:
|
||||
'Your Description should contain sufficient plain text or image alt-text, keeping it accessible to those using screen readers or with slow internet connections.',
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const { imageHeavy } = analyzeImageContent(context.project.body || '')
|
||||
return imageHeavy
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-description.title',
|
||||
defaultMessage: 'Edit description',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'missing-alt-text',
|
||||
title: defineMessage({
|
||||
id: 'nags.missing-alt-text.title',
|
||||
defaultMessage: 'Add image alt text',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.missing-alt-text.description',
|
||||
defaultMessage:
|
||||
'Some of your images are missing alt text, which is important for accessibility, especially for visually impaired users.',
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const { hasEmptyAltText } = analyzeImageContent(context.project.body || '')
|
||||
return hasEmptyAltText
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-description.title',
|
||||
defaultMessage: 'Edit description',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
return hasLoader || hasVersionPattern
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-title.title',
|
||||
defaultMessage: 'Edit title',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'summary-same-as-title',
|
||||
title: defineMessage({
|
||||
id: 'nags.summary-same-as-title.title',
|
||||
defaultMessage: 'Make the summary unique',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.summary-same-as-title.description',
|
||||
defaultMessage:
|
||||
"Your summary can not be the same as your project's Name. It's important to create an informative and enticing Summary.",
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const title = context.project.title?.trim() || ''
|
||||
const summary = context.project.description?.trim() || ''
|
||||
return title === summary && title.length > 0 && summary.length > 0
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-summary.title',
|
||||
defaultMessage: 'Edit summary',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Don't like this one, is this needed?
|
||||
id: 'image-heavy-description',
|
||||
title: defineMessage({
|
||||
id: 'nags.image-heavy-description.title',
|
||||
defaultMessage: 'Ensure accessibility',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.image-heavy-description.description',
|
||||
defaultMessage:
|
||||
'Your Description should contain sufficient plain text or image alt-text, keeping it accessible to those using screen readers or with slow internet connections.',
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const { imageHeavy } = analyzeImageContent(context.project.body || '')
|
||||
return imageHeavy
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-description.title',
|
||||
defaultMessage: 'Edit description',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'missing-alt-text',
|
||||
title: defineMessage({
|
||||
id: 'nags.missing-alt-text.title',
|
||||
defaultMessage: 'Add image alt text',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.missing-alt-text.description',
|
||||
defaultMessage:
|
||||
'Some of your images are missing alt text, which is important for accessibility, especially for visually impaired users.',
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const { hasEmptyAltText } = analyzeImageContent(context.project.body || '')
|
||||
return hasEmptyAltText
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-description.title',
|
||||
defaultMessage: 'Edit description',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './core'
|
||||
export * from './links'
|
||||
export * from './description'
|
||||
export * from './links'
|
||||
export * from './tags'
|
||||
|
||||
@@ -1,281 +1,282 @@
|
||||
import type { Nag, NagContext } from '../../types/nags'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
import { useVIntl, defineMessage } from '@vintl/vintl'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
|
||||
import type { Nag, NagContext } from '../../types/nags'
|
||||
|
||||
export const commonLinkDomains = {
|
||||
source: ['github.com', 'gitlab.com', 'bitbucket.org', 'codeberg.org', 'git.sr.ht'],
|
||||
issues: ['github.com', 'gitlab.com', 'bitbucket.org', 'codeberg.org', 'docs.google.com'],
|
||||
discord: ['discord.gg', 'discord.com', 'dsc.gg'],
|
||||
licenseBlocklist: [
|
||||
'youtube.com',
|
||||
'youtu.be',
|
||||
'modrinth.com',
|
||||
'curseforge.com',
|
||||
'twitter.com',
|
||||
'x.com',
|
||||
'discord.gg',
|
||||
'discord.com',
|
||||
'instagram.com',
|
||||
'facebook.com',
|
||||
'tiktok.com',
|
||||
'reddit.com',
|
||||
'twitch.tv',
|
||||
'patreon.com',
|
||||
'ko-fi.com',
|
||||
'paypal.com',
|
||||
'buymeacoffee.com',
|
||||
'google.com',
|
||||
'example.com',
|
||||
't.me',
|
||||
],
|
||||
linkShorteners: ['bit.ly', 'adf.ly', 'tinyurl.com', 'short.io', 'is.gd'],
|
||||
source: ['github.com', 'gitlab.com', 'bitbucket.org', 'codeberg.org', 'git.sr.ht'],
|
||||
issues: ['github.com', 'gitlab.com', 'bitbucket.org', 'codeberg.org', 'docs.google.com'],
|
||||
discord: ['discord.gg', 'discord.com', 'dsc.gg'],
|
||||
licenseBlocklist: [
|
||||
'youtube.com',
|
||||
'youtu.be',
|
||||
'modrinth.com',
|
||||
'curseforge.com',
|
||||
'twitter.com',
|
||||
'x.com',
|
||||
'discord.gg',
|
||||
'discord.com',
|
||||
'instagram.com',
|
||||
'facebook.com',
|
||||
'tiktok.com',
|
||||
'reddit.com',
|
||||
'twitch.tv',
|
||||
'patreon.com',
|
||||
'ko-fi.com',
|
||||
'paypal.com',
|
||||
'buymeacoffee.com',
|
||||
'google.com',
|
||||
'example.com',
|
||||
't.me',
|
||||
],
|
||||
linkShorteners: ['bit.ly', 'adf.ly', 'tinyurl.com', 'short.io', 'is.gd'],
|
||||
}
|
||||
|
||||
export function isCommonUrl(url: string | null, commonDomains: string[]): boolean {
|
||||
if (url === null || url === '') return true
|
||||
try {
|
||||
const domain = new URL(url).hostname.toLowerCase()
|
||||
return commonDomains.some((allowed) => domain.includes(allowed))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
if (url === null || url === '') return true
|
||||
try {
|
||||
const domain = new URL(url).hostname.toLowerCase()
|
||||
return commonDomains.some((allowed) => domain.includes(allowed))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isCommonUrlOfType(url: string | null, commonDomains: string[]): boolean {
|
||||
if (url === null || url === '') return false
|
||||
return isCommonUrl(url, commonDomains)
|
||||
if (url === null || url === '') return false
|
||||
return isCommonUrl(url, commonDomains)
|
||||
}
|
||||
|
||||
export function isDiscordUrl(url: string | null): boolean {
|
||||
return isCommonUrlOfType(url, commonLinkDomains.discord)
|
||||
return isCommonUrlOfType(url, commonLinkDomains.discord)
|
||||
}
|
||||
|
||||
export function isLinkShortener(url: string | null): boolean {
|
||||
return isCommonUrlOfType(url, commonLinkDomains.linkShorteners)
|
||||
return isCommonUrlOfType(url, commonLinkDomains.linkShorteners)
|
||||
}
|
||||
|
||||
export function isUncommonLicenseUrl(url: string | null): boolean {
|
||||
return isCommonUrlOfType(url, commonLinkDomains.licenseBlocklist)
|
||||
return isCommonUrlOfType(url, commonLinkDomains.licenseBlocklist)
|
||||
}
|
||||
|
||||
export const linksNags: Nag[] = [
|
||||
{
|
||||
id: 'verify-external-links',
|
||||
title: defineMessage({
|
||||
id: 'nags.verify-external-links.title',
|
||||
defaultMessage: 'Verify external links',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.verify-external-links.description',
|
||||
defaultMessage:
|
||||
'Some of your external links may be using domains that are inappropriate for that type of link.',
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
return (
|
||||
!isCommonUrl(context.project.source_url ?? null, commonLinkDomains.source) ||
|
||||
!isCommonUrl(context.project.issues_url ?? null, commonLinkDomains.issues) ||
|
||||
!isCommonUrl(context.project.discord_url ?? null, commonLinkDomains.discord)
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: defineMessage({
|
||||
id: 'nags.visit-links-settings.title',
|
||||
defaultMessage: 'Visit links settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'misused-discord-link',
|
||||
title: defineMessage({
|
||||
id: 'nags.misused-discord-link.title',
|
||||
defaultMessage: 'Move Discord invite',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.misused-discord-link-description',
|
||||
defaultMessage:
|
||||
'Discord invites can not be used for other link types. Please put your Discord link in the Discord Invite link field only.',
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) =>
|
||||
isDiscordUrl(context.project.source_url ?? null) ||
|
||||
isDiscordUrl(context.project.issues_url ?? null) ||
|
||||
isDiscordUrl(context.project.wiki_url ?? null),
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: defineMessage({
|
||||
id: 'nags.visit-links-settings.title',
|
||||
defaultMessage: 'Visit links settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'link-shortener-usage',
|
||||
title: defineMessage({
|
||||
id: 'nags.link-shortener-usage.title',
|
||||
defaultMessage: "Don't use link shorteners",
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.link-shortener-usage.description',
|
||||
defaultMessage:
|
||||
'Use of link shorteners or other methods to obscure where a link may lead in your external links or license link is prohibited, please only use appropriate full length links.',
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
if (context.project.donation_urls) {
|
||||
for (const donation of context.project.donation_urls) {
|
||||
if (isLinkShortener(donation.url ?? null)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
id: 'verify-external-links',
|
||||
title: defineMessage({
|
||||
id: 'nags.verify-external-links.title',
|
||||
defaultMessage: 'Verify external links',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.verify-external-links.description',
|
||||
defaultMessage:
|
||||
'Some of your external links may be using domains that are inappropriate for that type of link.',
|
||||
}),
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
return (
|
||||
!isCommonUrl(context.project.source_url ?? null, commonLinkDomains.source) ||
|
||||
!isCommonUrl(context.project.issues_url ?? null, commonLinkDomains.issues) ||
|
||||
!isCommonUrl(context.project.discord_url ?? null, commonLinkDomains.discord)
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: defineMessage({
|
||||
id: 'nags.visit-links-settings.title',
|
||||
defaultMessage: 'Visit links settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'misused-discord-link',
|
||||
title: defineMessage({
|
||||
id: 'nags.misused-discord-link.title',
|
||||
defaultMessage: 'Move Discord invite',
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.misused-discord-link-description',
|
||||
defaultMessage:
|
||||
'Discord invites can not be used for other link types. Please put your Discord link in the Discord Invite link field only.',
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) =>
|
||||
isDiscordUrl(context.project.source_url ?? null) ||
|
||||
isDiscordUrl(context.project.issues_url ?? null) ||
|
||||
isDiscordUrl(context.project.wiki_url ?? null),
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: defineMessage({
|
||||
id: 'nags.visit-links-settings.title',
|
||||
defaultMessage: 'Visit links settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'link-shortener-usage',
|
||||
title: defineMessage({
|
||||
id: 'nags.link-shortener-usage.title',
|
||||
defaultMessage: "Don't use link shorteners",
|
||||
}),
|
||||
description: defineMessage({
|
||||
id: 'nags.link-shortener-usage.description',
|
||||
defaultMessage:
|
||||
'Use of link shorteners or other methods to obscure where a link may lead in your external links or license link is prohibited, please only use appropriate full length links.',
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
if (context.project.donation_urls) {
|
||||
for (const donation of context.project.donation_urls) {
|
||||
if (isLinkShortener(donation.url ?? null)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
isLinkShortener(context.project.source_url ?? null) ||
|
||||
isLinkShortener(context.project.issues_url ?? null) ||
|
||||
isLinkShortener(context.project.wiki_url ?? null) ||
|
||||
isLinkShortener(context.project.discord_url ?? null) ||
|
||||
Boolean(context.project.license.url && isLinkShortener(context.project.license.url ?? null))
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'invalid-license-url',
|
||||
title: defineMessage({
|
||||
id: 'nags.invalid-license-url.title',
|
||||
defaultMessage: 'Add a valid license link',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const licenseUrl = context.project.license.url
|
||||
return (
|
||||
isLinkShortener(context.project.source_url ?? null) ||
|
||||
isLinkShortener(context.project.issues_url ?? null) ||
|
||||
isLinkShortener(context.project.wiki_url ?? null) ||
|
||||
isLinkShortener(context.project.discord_url ?? null) ||
|
||||
Boolean(context.project.license.url && isLinkShortener(context.project.license.url ?? null))
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'invalid-license-url',
|
||||
title: defineMessage({
|
||||
id: 'nags.invalid-license-url.title',
|
||||
defaultMessage: 'Add a valid license link',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const licenseUrl = context.project.license.url
|
||||
|
||||
if (!licenseUrl) {
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.invalid-license-url.description.default',
|
||||
defaultMessage: 'License URL is invalid.',
|
||||
}),
|
||||
)
|
||||
}
|
||||
if (!licenseUrl) {
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.invalid-license-url.description.default',
|
||||
defaultMessage: 'License URL is invalid.',
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const domain = new URL(licenseUrl).hostname.toLowerCase()
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.invalid-license-url.description.domain',
|
||||
defaultMessage:
|
||||
'Your license URL points to {domain}, which is not appropriate for license information. License URLs should link directly to your license file, not social media, gaming platforms, etc.',
|
||||
}),
|
||||
{ domain },
|
||||
)
|
||||
} catch {
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.invalid-license-url.description.malformed',
|
||||
defaultMessage:
|
||||
'Your license URL appears to be malformed. Please provide a valid URL to your license text.',
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const licenseUrl = context.project.license.url
|
||||
if (!licenseUrl) return false
|
||||
try {
|
||||
const domain = new URL(licenseUrl).hostname.toLowerCase()
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.invalid-license-url.description.domain',
|
||||
defaultMessage:
|
||||
'Your license URL points to {domain}, which is not appropriate for license information. License URLs should link directly to your license file, not social media, gaming platforms, etc.',
|
||||
}),
|
||||
{ domain },
|
||||
)
|
||||
} catch {
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.invalid-license-url.description.malformed',
|
||||
defaultMessage:
|
||||
'Your license URL appears to be malformed. Please provide a valid URL to your license text.',
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const licenseUrl = context.project.license.url
|
||||
if (!licenseUrl) return false
|
||||
|
||||
const isBlocklisted = isUncommonLicenseUrl(licenseUrl)
|
||||
const isBlocklisted = isUncommonLicenseUrl(licenseUrl)
|
||||
|
||||
try {
|
||||
new URL(licenseUrl)
|
||||
return isBlocklisted
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-license.title',
|
||||
defaultMessage: 'Edit license',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gpl-license-source-required',
|
||||
title: defineMessage({
|
||||
id: 'nags.gpl-license-source-required.title',
|
||||
defaultMessage: 'Provide source code',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
try {
|
||||
new URL(licenseUrl)
|
||||
return isBlocklisted
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
},
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-license.title',
|
||||
defaultMessage: 'Edit license',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gpl-license-source-required',
|
||||
title: defineMessage({
|
||||
id: 'nags.gpl-license-source-required.title',
|
||||
defaultMessage: 'Provide source code',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.gpl-license-source-required.description',
|
||||
defaultMessage:
|
||||
'Your {projectType} uses a license which requires source code to be available. Please provide a source code link or sources file for each additional version, or consider using a different license.',
|
||||
}),
|
||||
{
|
||||
projectType: formatProjectType(context.project.project_type).toLowerCase(),
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const gplLicenses = [
|
||||
'GPL-2.0',
|
||||
'GPL-2.0+',
|
||||
'GPL-2.0-only',
|
||||
'GPL-2.0-or-later',
|
||||
'GPL-3.0',
|
||||
'GPL-3.0+',
|
||||
'GPL-3.0-only',
|
||||
'GPL-3.0-or-later',
|
||||
'LGPL-2.1',
|
||||
'LGPL-2.1+',
|
||||
'LGPL-2.1-only',
|
||||
'LGPL-2.1-or-later',
|
||||
'LGPL-3.0',
|
||||
'LGPL-3.0+',
|
||||
'LGPL-3.0-only',
|
||||
'LGPL-3.0-or-later',
|
||||
'AGPL-3.0',
|
||||
'AGPL-3.0+',
|
||||
'AGPL-3.0-only',
|
||||
'AGPL-3.0-or-later',
|
||||
'MPL-2.0',
|
||||
]
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.gpl-license-source-required.description',
|
||||
defaultMessage:
|
||||
'Your {projectType} uses a license which requires source code to be available. Please provide a source code link or sources file for each additional version, or consider using a different license.',
|
||||
}),
|
||||
{
|
||||
projectType: formatProjectType(context.project.project_type).toLowerCase(),
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const gplLicenses = [
|
||||
'GPL-2.0',
|
||||
'GPL-2.0+',
|
||||
'GPL-2.0-only',
|
||||
'GPL-2.0-or-later',
|
||||
'GPL-3.0',
|
||||
'GPL-3.0+',
|
||||
'GPL-3.0-only',
|
||||
'GPL-3.0-or-later',
|
||||
'LGPL-2.1',
|
||||
'LGPL-2.1+',
|
||||
'LGPL-2.1-only',
|
||||
'LGPL-2.1-or-later',
|
||||
'LGPL-3.0',
|
||||
'LGPL-3.0+',
|
||||
'LGPL-3.0-only',
|
||||
'LGPL-3.0-or-later',
|
||||
'AGPL-3.0',
|
||||
'AGPL-3.0+',
|
||||
'AGPL-3.0-only',
|
||||
'AGPL-3.0-or-later',
|
||||
'MPL-2.0',
|
||||
]
|
||||
|
||||
const isGplLicense = gplLicenses.includes(context.project.license.id)
|
||||
const hasSourceUrl = !!context.project.source_url
|
||||
const hasAdditionalFiles = (context: NagContext) => {
|
||||
let hasAdditional = true
|
||||
context.versions.forEach((version) => {
|
||||
if (version.files.length < 2) hasAdditional = false
|
||||
})
|
||||
return hasAdditional
|
||||
}
|
||||
const notSourceAsDistributed = (context: NagContext) =>
|
||||
context.project.project_type === 'mod' || context.project.project_type === 'plugin'
|
||||
const isGplLicense = gplLicenses.includes(context.project.license.id)
|
||||
const hasSourceUrl = !!context.project.source_url
|
||||
const hasAdditionalFiles = (context: NagContext) => {
|
||||
let hasAdditional = true
|
||||
context.versions.forEach((version) => {
|
||||
if (version.files.length < 2) hasAdditional = false
|
||||
})
|
||||
return hasAdditional
|
||||
}
|
||||
const notSourceAsDistributed = (context: NagContext) =>
|
||||
context.project.project_type === 'mod' || context.project.project_type === 'plugin'
|
||||
|
||||
return (
|
||||
isGplLicense &&
|
||||
notSourceAsDistributed(context) &&
|
||||
!hasSourceUrl &&
|
||||
!hasAdditionalFiles(context)
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: defineMessage({
|
||||
id: 'nags.visit-links-settings.title',
|
||||
defaultMessage: 'Visit links settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
return (
|
||||
isGplLicense &&
|
||||
notSourceAsDistributed(context) &&
|
||||
!hasSourceUrl &&
|
||||
!hasAdditionalFiles(context)
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: defineMessage({
|
||||
id: 'nags.visit-links-settings.title',
|
||||
defaultMessage: 'Visit links settings',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,160 +1,161 @@
|
||||
import type { Project } from '@modrinth/utils'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
|
||||
import type { Nag, NagContext } from '../../types/nags'
|
||||
import { useVIntl, defineMessage } from '@vintl/vintl'
|
||||
|
||||
const allResolutionTags = ['8x-', '16x', '32x', '48x', '64x', '128x', '256x', '512x+']
|
||||
|
||||
const MAX_TAG_COUNT = 8
|
||||
|
||||
function getCategories(
|
||||
project: Project & { actualProjectType: string },
|
||||
tags: {
|
||||
categories?: {
|
||||
project_type: string
|
||||
}[]
|
||||
},
|
||||
project: Project & { actualProjectType: string },
|
||||
tags: {
|
||||
categories?: {
|
||||
project_type: string
|
||||
}[]
|
||||
},
|
||||
) {
|
||||
return (
|
||||
tags.categories?.filter(
|
||||
(category: { project_type: string }) => category.project_type === project.actualProjectType,
|
||||
) ?? []
|
||||
)
|
||||
return (
|
||||
tags.categories?.filter(
|
||||
(category: { project_type: string }) => category.project_type === project.actualProjectType,
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
export const tagsNags: Nag[] = [
|
||||
{
|
||||
id: 'too-many-tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.too-many-tags.title',
|
||||
defaultMessage: 'Select accurate tags',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const tagCount =
|
||||
context.project.categories.length + (context.project.additional_categories?.length || 0)
|
||||
const maxTagCount = MAX_TAG_COUNT
|
||||
{
|
||||
id: 'too-many-tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.too-many-tags.title',
|
||||
defaultMessage: 'Select accurate tags',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const tagCount =
|
||||
context.project.categories.length + (context.project.additional_categories?.length || 0)
|
||||
const maxTagCount = MAX_TAG_COUNT
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.too-many-tags.description',
|
||||
defaultMessage:
|
||||
"You've selected {tagCount} tags. Consider reducing to {maxTagCount} or fewer to make sure your project appears in relevant search results.",
|
||||
}),
|
||||
{
|
||||
tagCount,
|
||||
maxTagCount,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const tagCount =
|
||||
context.project.categories.length + (context.project.additional_categories?.length || 0)
|
||||
return tagCount > MAX_TAG_COUNT
|
||||
},
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-tags.title',
|
||||
defaultMessage: 'Edit tags',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'multiple-resolution-tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.multiple-resolution-tags.title',
|
||||
defaultMessage: 'Select correct resolution',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const resolutionTags = context.project.categories
|
||||
.concat(context.project.additional_categories)
|
||||
.filter((tag: string) => allResolutionTags.includes(tag))
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.too-many-tags.description',
|
||||
defaultMessage:
|
||||
"You've selected {tagCount} tags. Consider reducing to {maxTagCount} or fewer to make sure your project appears in relevant search results.",
|
||||
}),
|
||||
{
|
||||
tagCount,
|
||||
maxTagCount,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const tagCount =
|
||||
context.project.categories.length + (context.project.additional_categories?.length || 0)
|
||||
return tagCount > MAX_TAG_COUNT
|
||||
},
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-tags.title',
|
||||
defaultMessage: 'Edit tags',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'multiple-resolution-tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.multiple-resolution-tags.title',
|
||||
defaultMessage: 'Select correct resolution',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const resolutionTags = context.project.categories
|
||||
.concat(context.project.additional_categories)
|
||||
.filter((tag: string) => allResolutionTags.includes(tag))
|
||||
|
||||
const sortedTags = resolutionTags.toSorted((a, b) => {
|
||||
return allResolutionTags.indexOf(a) - allResolutionTags.indexOf(b)
|
||||
})
|
||||
const sortedTags = resolutionTags.toSorted((a, b) => {
|
||||
return allResolutionTags.indexOf(a) - allResolutionTags.indexOf(b)
|
||||
})
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.multiple-resolution-tags.description',
|
||||
defaultMessage:
|
||||
"You've selected {count} resolution tags ({tags}). Resource packs should typically only have one resolution tag that matches their primary resolution.",
|
||||
}),
|
||||
{
|
||||
count: resolutionTags.length,
|
||||
tags: sortedTags
|
||||
.join(', ')
|
||||
.replace('8x-', '8x or lower')
|
||||
.replace('512x+', '512x or higher'),
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
if (context.project.project_type !== 'resourcepack') return false
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.multiple-resolution-tags.description',
|
||||
defaultMessage:
|
||||
"You've selected {count} resolution tags ({tags}). Resource packs should typically only have one resolution tag that matches their primary resolution.",
|
||||
}),
|
||||
{
|
||||
count: resolutionTags.length,
|
||||
tags: sortedTags
|
||||
.join(', ')
|
||||
.replace('8x-', '8x or lower')
|
||||
.replace('512x+', '512x or higher'),
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
if (context.project.project_type !== 'resourcepack') return false
|
||||
|
||||
const resolutionTags = context.project.categories
|
||||
.concat(context.project.additional_categories)
|
||||
.filter((tag: string) => allResolutionTags.includes(tag))
|
||||
return resolutionTags.length > 1
|
||||
},
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-tags.title',
|
||||
defaultMessage: 'Edit tags',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'all-tags-selected',
|
||||
title: defineMessage({
|
||||
id: 'nags.all-tags-selected.title',
|
||||
defaultMessage: 'Select accurate tags',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const categoriesForProjectType = getCategories(
|
||||
context.project as Project & { actualProjectType: string },
|
||||
context.tags,
|
||||
)
|
||||
const totalAvailableTags = categoriesForProjectType.length
|
||||
const resolutionTags = context.project.categories
|
||||
.concat(context.project.additional_categories)
|
||||
.filter((tag: string) => allResolutionTags.includes(tag))
|
||||
return resolutionTags.length > 1
|
||||
},
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-tags.title',
|
||||
defaultMessage: 'Edit tags',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'all-tags-selected',
|
||||
title: defineMessage({
|
||||
id: 'nags.all-tags-selected.title',
|
||||
defaultMessage: 'Select accurate tags',
|
||||
}),
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const categoriesForProjectType = getCategories(
|
||||
context.project as Project & { actualProjectType: string },
|
||||
context.tags,
|
||||
)
|
||||
const totalAvailableTags = categoriesForProjectType.length
|
||||
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.all-tags-selected.description',
|
||||
defaultMessage:
|
||||
"You've selected all {totalAvailableTags} available tags. This defeats the purpose of tags, which are meant to help users find relevant projects. Please select only the tags that are relevant to your project.",
|
||||
}),
|
||||
{
|
||||
totalAvailableTags,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const categoriesForProjectType = getCategories(
|
||||
context.project as Project & { actualProjectType: string },
|
||||
context.tags,
|
||||
)
|
||||
const totalSelectedTags =
|
||||
context.project.categories.length + (context.project.additional_categories?.length || 0)
|
||||
return (
|
||||
totalSelectedTags === categoriesForProjectType.length &&
|
||||
context.project.project_type !== 'project'
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-tags.title',
|
||||
defaultMessage: 'Edit tags',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
return formatMessage(
|
||||
defineMessage({
|
||||
id: 'nags.all-tags-selected.description',
|
||||
defaultMessage:
|
||||
"You've selected all {totalAvailableTags} available tags. This defeats the purpose of tags, which are meant to help users find relevant projects. Please select only the tags that are relevant to your project.",
|
||||
}),
|
||||
{
|
||||
totalAvailableTags,
|
||||
},
|
||||
)
|
||||
},
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) => {
|
||||
const categoriesForProjectType = getCategories(
|
||||
context.project as Project & { actualProjectType: string },
|
||||
context.tags,
|
||||
)
|
||||
const totalSelectedTags =
|
||||
context.project.categories.length + (context.project.additional_categories?.length || 0)
|
||||
return (
|
||||
totalSelectedTags === categoriesForProjectType.length &&
|
||||
context.project.project_type !== 'project'
|
||||
)
|
||||
},
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: defineMessage({
|
||||
id: 'nags.edit-tags.title',
|
||||
defaultMessage: 'Edit tags',
|
||||
}),
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import type { ReportQuickReply } from '../types/reports'
|
||||
|
||||
export default [
|
||||
{
|
||||
label: 'Antivirus',
|
||||
message: async () => (await import('./messages/reports/antivirus.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Spam',
|
||||
message: async () => (await import('./messages/reports/spam.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Gameplay Issue',
|
||||
message: async () => (await import('./messages/reports/gameplay-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Platform Issue',
|
||||
message: async () => (await import('./messages/reports/platform-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Stale',
|
||||
message: async () => (await import('./messages/reports/stale.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Confirmed Malware',
|
||||
message: async () => (await import('./messages/reports/confirmed-malware.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Antivirus',
|
||||
message: async () => (await import('./messages/reports/antivirus.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Spam',
|
||||
message: async () => (await import('./messages/reports/spam.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Gameplay Issue',
|
||||
message: async () => (await import('./messages/reports/gameplay-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Platform Issue',
|
||||
message: async () => (await import('./messages/reports/platform-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Stale',
|
||||
message: async () => (await import('./messages/reports/stale.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Confirmed Malware',
|
||||
message: async () => (await import('./messages/reports/confirmed-malware.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
] as ReadonlyArray<ReportQuickReply>
|
||||
|
||||
@@ -1,58 +1,59 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { TagsIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const categories: Stage = {
|
||||
title: "Are the project's tags accurate?",
|
||||
id: 'tags',
|
||||
icon: TagsIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings/tags',
|
||||
shouldShow: (project) =>
|
||||
project.categories.length > 0 || project.additional_categories.length > 0,
|
||||
text: async () => {
|
||||
return (await import('../messages/checklist-text/categories.md?raw')).default
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: 'categories_inaccurate',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 700,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/categories/inaccurate.md?raw')).default,
|
||||
disablesActions: ['categories_optimization_misused', 'categories_resolutions_misused'],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'categories_optimization_misused',
|
||||
type: 'button',
|
||||
label: 'Optimization',
|
||||
weight: 701,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) =>
|
||||
project.categories.includes('optimization') ||
|
||||
project.additional_categories.includes('optimization'),
|
||||
message: async () =>
|
||||
(await import('../messages/categories/inaccurate.md?raw')).default +
|
||||
(await import('../messages/categories/optimization_misused.md?raw')).default,
|
||||
disablesActions: ['categories_inaccurate', 'categories_resolutions_misused'],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'categories_resolutions_misused',
|
||||
type: 'button',
|
||||
label: 'Resolutions',
|
||||
weight: 702,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'resourcepack',
|
||||
message: async () =>
|
||||
(await import('../messages/categories/inaccurate.md?raw')).default +
|
||||
(await import('../messages/categories/resolutions_misused.md?raw')).default,
|
||||
disablesActions: ['categories_inaccurate', 'categories_optimization_misused'],
|
||||
},
|
||||
],
|
||||
title: "Are the project's tags accurate?",
|
||||
id: 'tags',
|
||||
icon: TagsIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings/tags',
|
||||
shouldShow: (project) =>
|
||||
project.categories.length > 0 || project.additional_categories.length > 0,
|
||||
text: async () => {
|
||||
return (await import('../messages/checklist-text/categories.md?raw')).default
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: 'categories_inaccurate',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 700,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/categories/inaccurate.md?raw')).default,
|
||||
disablesActions: ['categories_optimization_misused', 'categories_resolutions_misused'],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'categories_optimization_misused',
|
||||
type: 'button',
|
||||
label: 'Optimization',
|
||||
weight: 701,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) =>
|
||||
project.categories.includes('optimization') ||
|
||||
project.additional_categories.includes('optimization'),
|
||||
message: async () =>
|
||||
(await import('../messages/categories/inaccurate.md?raw')).default +
|
||||
(await import('../messages/categories/optimization_misused.md?raw')).default,
|
||||
disablesActions: ['categories_inaccurate', 'categories_resolutions_misused'],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'categories_resolutions_misused',
|
||||
type: 'button',
|
||||
label: 'Resolutions',
|
||||
weight: 702,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'resourcepack',
|
||||
message: async () =>
|
||||
(await import('../messages/categories/inaccurate.md?raw')).default +
|
||||
(await import('../messages/categories/resolutions_misused.md?raw')).default,
|
||||
disablesActions: ['categories_inaccurate', 'categories_optimization_misused'],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default categories
|
||||
|
||||
@@ -1,109 +1,110 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { LibraryIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const description: Stage = {
|
||||
title: 'Is the description sufficient, accurate, and accessible?',
|
||||
id: 'description',
|
||||
icon: LibraryIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#general-expectations',
|
||||
navigate: '/',
|
||||
actions: [
|
||||
{
|
||||
id: 'description_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient (custom)',
|
||||
weight: 400,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/insufficient.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Please elaborate on how the author can improve their description.',
|
||||
variable: 'EXPLAINER',
|
||||
large: true,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_insufficient_packs',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 401,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/description/insufficient-packs.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_insufficient_projects',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 401,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => project.project_type !== 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/description/insufficient-projects.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_non_english',
|
||||
type: 'button',
|
||||
label: 'Non-english',
|
||||
weight: 402,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/non-english.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_unfinished',
|
||||
type: 'button',
|
||||
label: 'Unfinished',
|
||||
weight: 403,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/description/unfinished.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_headers_as_body',
|
||||
type: 'button',
|
||||
label: 'Headers as body text',
|
||||
weight: 404,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/description/headers-as-body.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_image_only',
|
||||
type: 'button',
|
||||
label: 'Image-only',
|
||||
weight: 405,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/image-only.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_non_standard_text',
|
||||
type: 'button',
|
||||
label: 'Non-standard text',
|
||||
weight: 406,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/description/non-standard-text.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_clarity',
|
||||
type: 'button',
|
||||
label: 'Unclear / Misleading',
|
||||
weight: 407,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/description/clarity.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
title: 'Is the description sufficient, accurate, and accessible?',
|
||||
id: 'description',
|
||||
icon: LibraryIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#general-expectations',
|
||||
navigate: '/',
|
||||
actions: [
|
||||
{
|
||||
id: 'description_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient (custom)',
|
||||
weight: 400,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/insufficient.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Please elaborate on how the author can improve their description.',
|
||||
variable: 'EXPLAINER',
|
||||
large: true,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_insufficient_packs',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 401,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/description/insufficient-packs.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_insufficient_projects',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 401,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => project.project_type !== 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/description/insufficient-projects.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_non_english',
|
||||
type: 'button',
|
||||
label: 'Non-english',
|
||||
weight: 402,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/non-english.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_unfinished',
|
||||
type: 'button',
|
||||
label: 'Unfinished',
|
||||
weight: 403,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/description/unfinished.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_headers_as_body',
|
||||
type: 'button',
|
||||
label: 'Headers as body text',
|
||||
weight: 404,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/description/headers-as-body.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_image_only',
|
||||
type: 'button',
|
||||
label: 'Image-only',
|
||||
weight: 405,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/image-only.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_non_standard_text',
|
||||
type: 'button',
|
||||
label: 'Non-standard text',
|
||||
weight: 406,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/description/non-standard-text.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_clarity',
|
||||
type: 'button',
|
||||
label: 'Unclear / Misleading',
|
||||
weight: 407,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/description/clarity.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default description
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { ImageIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const gallery: Stage = {
|
||||
title: "Are this project's gallery images sufficient?",
|
||||
id: 'gallery',
|
||||
icon: ImageIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#general-expectations',
|
||||
navigate: '/gallery',
|
||||
actions: [
|
||||
{
|
||||
id: 'gallery_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 900,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/gallery/insufficient.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'gallery_not_relevant',
|
||||
type: 'button',
|
||||
label: 'Not relevant',
|
||||
weight: 901,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.gallery && project.gallery.length > 0,
|
||||
message: async () => (await import('../messages/gallery/not-relevant.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
title: "Are this project's gallery images sufficient?",
|
||||
id: 'gallery',
|
||||
icon: ImageIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#general-expectations',
|
||||
navigate: '/gallery',
|
||||
actions: [
|
||||
{
|
||||
id: 'gallery_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 900,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/gallery/insufficient.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'gallery_not_relevant',
|
||||
type: 'button',
|
||||
label: 'Not relevant',
|
||||
weight: 901,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.gallery && project.gallery.length > 0,
|
||||
message: async () => (await import('../messages/gallery/not-relevant.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default gallery
|
||||
|
||||
@@ -1,84 +1,85 @@
|
||||
import { BookTextIcon } from '@modrinth/assets'
|
||||
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const licensesNotRequiringSource: string[] = [
|
||||
'LicenseRef-All-Rights-Reserved',
|
||||
'Apache-2.0',
|
||||
'BSD-2-Clause',
|
||||
'BSD-3-Clause',
|
||||
'CC0-1.0',
|
||||
'CC-BY-4.0',
|
||||
'CC-BY-SA-4.0',
|
||||
'CC-BY-NC-4.0',
|
||||
'CC-BY-NC-SA-4.0',
|
||||
'CC-BY-ND-4.0',
|
||||
'CC-BY-NC-ND-4.0',
|
||||
'ISC',
|
||||
'MIT',
|
||||
'Zlib',
|
||||
'LicenseRef-All-Rights-Reserved',
|
||||
'Apache-2.0',
|
||||
'BSD-2-Clause',
|
||||
'BSD-3-Clause',
|
||||
'CC0-1.0',
|
||||
'CC-BY-4.0',
|
||||
'CC-BY-SA-4.0',
|
||||
'CC-BY-NC-4.0',
|
||||
'CC-BY-NC-SA-4.0',
|
||||
'CC-BY-ND-4.0',
|
||||
'CC-BY-NC-ND-4.0',
|
||||
'ISC',
|
||||
'MIT',
|
||||
'Zlib',
|
||||
]
|
||||
|
||||
const licenseStage: Stage = {
|
||||
title: 'Is this license and link valid?',
|
||||
text: async () => (await import('../messages/checklist-text/licensing.md?raw')).default,
|
||||
id: 'license',
|
||||
icon: BookTextIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings/license',
|
||||
actions: [
|
||||
{
|
||||
id: 'license_invalid_link',
|
||||
type: 'button',
|
||||
label: 'Invalid Link',
|
||||
weight: 600,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => Boolean(project.license.url),
|
||||
message: async () => (await import('../messages/license/invalid_link.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'license_invalid_link-custom_license',
|
||||
type: 'toggle',
|
||||
label: 'Invalid Link: Custom License',
|
||||
weight: 601,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/license/invalid_link-custom_license.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'license_no_source',
|
||||
type: 'conditional-button',
|
||||
label: 'No Source',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => !licensesNotRequiringSource.includes(project.license.id),
|
||||
messageVariants: [
|
||||
{
|
||||
conditions: {
|
||||
excludedActions: ['license_no_source-fork'],
|
||||
},
|
||||
weight: 602,
|
||||
message: async () => (await import('../messages/license/no_source.md?raw')).default,
|
||||
},
|
||||
],
|
||||
fallbackWeight: 602,
|
||||
fallbackMessage: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'license_no_source-fork',
|
||||
type: 'toggle',
|
||||
label: 'No Source: Fork',
|
||||
weight: 602,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/license/no_source-fork.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
title: 'Is this license and link valid?',
|
||||
text: async () => (await import('../messages/checklist-text/licensing.md?raw')).default,
|
||||
id: 'license',
|
||||
icon: BookTextIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings/license',
|
||||
actions: [
|
||||
{
|
||||
id: 'license_invalid_link',
|
||||
type: 'button',
|
||||
label: 'Invalid Link',
|
||||
weight: 600,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => Boolean(project.license.url),
|
||||
message: async () => (await import('../messages/license/invalid_link.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'license_invalid_link-custom_license',
|
||||
type: 'toggle',
|
||||
label: 'Invalid Link: Custom License',
|
||||
weight: 601,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/license/invalid_link-custom_license.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'license_no_source',
|
||||
type: 'conditional-button',
|
||||
label: 'No Source',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => !licensesNotRequiringSource.includes(project.license.id),
|
||||
messageVariants: [
|
||||
{
|
||||
conditions: {
|
||||
excludedActions: ['license_no_source-fork'],
|
||||
},
|
||||
weight: 602,
|
||||
message: async () => (await import('../messages/license/no_source.md?raw')).default,
|
||||
},
|
||||
],
|
||||
fallbackWeight: 602,
|
||||
fallbackMessage: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'license_no_source-fork',
|
||||
type: 'toggle',
|
||||
label: 'No Source: Fork',
|
||||
weight: 602,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/license/no_source-fork.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default licenseStage
|
||||
|
||||
@@ -1,88 +1,89 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { LinkIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const links: Stage = {
|
||||
title: "Are the project's links accurate and accessible?",
|
||||
id: 'links',
|
||||
icon: LinkIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules',
|
||||
navigate: '/settings/links',
|
||||
shouldShow: (project) =>
|
||||
Boolean(
|
||||
project.issues_url ||
|
||||
project.source_url ||
|
||||
project.wiki_url ||
|
||||
project.discord_url ||
|
||||
project.donation_urls.length > 0,
|
||||
),
|
||||
text: async (project) => {
|
||||
let text = (await import('../messages/checklist-text/links/base.md?raw')).default
|
||||
title: "Are the project's links accurate and accessible?",
|
||||
id: 'links',
|
||||
icon: LinkIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules',
|
||||
navigate: '/settings/links',
|
||||
shouldShow: (project) =>
|
||||
Boolean(
|
||||
project.issues_url ||
|
||||
project.source_url ||
|
||||
project.wiki_url ||
|
||||
project.discord_url ||
|
||||
project.donation_urls.length > 0,
|
||||
),
|
||||
text: async (project) => {
|
||||
let text = (await import('../messages/checklist-text/links/base.md?raw')).default
|
||||
|
||||
if (project.donation_urls.length > 0) {
|
||||
text += (await import('../messages/checklist-text/links/donation/donations.md?raw')).default
|
||||
if (project.donation_urls.length > 0) {
|
||||
text += (await import('../messages/checklist-text/links/donation/donations.md?raw')).default
|
||||
|
||||
for (const donation of project.donation_urls) {
|
||||
text += (await import(`../messages/checklist-text/links/donation/donation.md?raw`)).default
|
||||
.replace('{URL}', donation.url)
|
||||
.replace('{PLATFORM}', donation.platform)
|
||||
}
|
||||
}
|
||||
for (const donation of project.donation_urls) {
|
||||
text += (await import(`../messages/checklist-text/links/donation/donation.md?raw`)).default
|
||||
.replace('{URL}', donation.url)
|
||||
.replace('{PLATFORM}', donation.platform)
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: 'links_misused',
|
||||
type: 'button',
|
||||
label: 'Links are misused',
|
||||
weight: 500,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/links/misused.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'What links are misused?',
|
||||
variable: 'MISUSED_LINKS',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'links_unaccessible',
|
||||
type: 'button',
|
||||
label: 'Links are inaccessible',
|
||||
weight: 510,
|
||||
suggestedStatus: 'flagged',
|
||||
// Theoretically a conditional could go here to prevent overlap of misuse and unaccessible messages repeating while still allowing for a multi-select in each.
|
||||
// if links_misused was selected, send nothing.
|
||||
message: async () => (await import('../messages/links/not_accessible.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'links_unaccessible_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Warn of inaccessible link?',
|
||||
shouldShow: (project) => Boolean(project.source_url || project.discord_url),
|
||||
options: [
|
||||
{
|
||||
label: 'Source',
|
||||
weight: 511,
|
||||
shouldShow: (project) => Boolean(project.source_url),
|
||||
message: async () =>
|
||||
(await import('../messages/links/not_accessible-source.md?raw')).default,
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
weight: 512,
|
||||
shouldShow: (project) => Boolean(project.discord_url),
|
||||
message: async () =>
|
||||
(await import('../messages/links/not_accessible-discord.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
return text
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: 'links_misused',
|
||||
type: 'button',
|
||||
label: 'Links are misused',
|
||||
weight: 500,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/links/misused.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'What links are misused?',
|
||||
variable: 'MISUSED_LINKS',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'links_unaccessible',
|
||||
type: 'button',
|
||||
label: 'Links are inaccessible',
|
||||
weight: 510,
|
||||
suggestedStatus: 'flagged',
|
||||
// Theoretically a conditional could go here to prevent overlap of misuse and unaccessible messages repeating while still allowing for a multi-select in each.
|
||||
// if links_misused was selected, send nothing.
|
||||
message: async () => (await import('../messages/links/not_accessible.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'links_unaccessible_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Warn of inaccessible link?',
|
||||
shouldShow: (project) => Boolean(project.source_url || project.discord_url),
|
||||
options: [
|
||||
{
|
||||
label: 'Source',
|
||||
weight: 511,
|
||||
shouldShow: (project) => Boolean(project.source_url),
|
||||
message: async () =>
|
||||
(await import('../messages/links/not_accessible-source.md?raw')).default,
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
weight: 512,
|
||||
shouldShow: (project) => Boolean(project.discord_url),
|
||||
message: async () =>
|
||||
(await import('../messages/links/not_accessible-discord.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default links
|
||||
|
||||
@@ -1,111 +1,112 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { CopyrightIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const reupload: Stage = {
|
||||
title: 'Does the author have proper permissions to post this project?',
|
||||
id: 'reupload',
|
||||
icon: CopyrightIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules',
|
||||
actions: [
|
||||
{
|
||||
id: 'reupload_reupload',
|
||||
type: 'button',
|
||||
label: 'Re-upload',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/reupload.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_unclear_fork',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'What is the title of the original project?',
|
||||
variable: 'ORIGINAL_PROJECT',
|
||||
required: true,
|
||||
suggestions: ['Vanilla Tweaks'],
|
||||
},
|
||||
{
|
||||
label: 'What is the author of the original project?',
|
||||
variable: 'ORIGINAL_AUTHOR',
|
||||
required: true,
|
||||
suggestions: ['Vanilla Tweaks Team'],
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_unclear_fork',
|
||||
type: 'button',
|
||||
label: 'Unclear Fork',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/fork.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_insufficient_fork',
|
||||
type: 'button',
|
||||
label: 'Insufficient Fork',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/insufficient_fork.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_unclear_fork',
|
||||
'reupload_reupload',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_request_proof',
|
||||
type: 'button',
|
||||
label: 'Proof of permissions',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () =>
|
||||
(await import('../messages/reupload/proof_of_permissions.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_unclear_fork',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'reupload_identity_verification',
|
||||
type: 'button',
|
||||
label: 'Verify Identity',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () =>
|
||||
(await import('../messages/reupload/identity_verification.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Where else can the project be found?',
|
||||
variable: 'PLATFORM',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
],
|
||||
},
|
||||
],
|
||||
title: 'Does the author have proper permissions to post this project?',
|
||||
id: 'reupload',
|
||||
icon: CopyrightIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules',
|
||||
actions: [
|
||||
{
|
||||
id: 'reupload_reupload',
|
||||
type: 'button',
|
||||
label: 'Re-upload',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/reupload.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_unclear_fork',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'What is the title of the original project?',
|
||||
variable: 'ORIGINAL_PROJECT',
|
||||
required: true,
|
||||
suggestions: ['Vanilla Tweaks'],
|
||||
},
|
||||
{
|
||||
label: 'What is the author of the original project?',
|
||||
variable: 'ORIGINAL_AUTHOR',
|
||||
required: true,
|
||||
suggestions: ['Vanilla Tweaks Team'],
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_unclear_fork',
|
||||
type: 'button',
|
||||
label: 'Unclear Fork',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/fork.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_insufficient_fork',
|
||||
type: 'button',
|
||||
label: 'Insufficient Fork',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/insufficient_fork.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_unclear_fork',
|
||||
'reupload_reupload',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_request_proof',
|
||||
type: 'button',
|
||||
label: 'Proof of permissions',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () =>
|
||||
(await import('../messages/reupload/proof_of_permissions.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_unclear_fork',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'reupload_identity_verification',
|
||||
type: 'button',
|
||||
label: 'Verify Identity',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () =>
|
||||
(await import('../messages/reupload/identity_verification.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Where else can the project be found?',
|
||||
variable: 'PLATFORM',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default reupload
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { ListBulletedIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const ruleFollowing: Stage = {
|
||||
title: 'Does this project violate the rules?',
|
||||
id: 'rule-following',
|
||||
icon: ListBulletedIcon,
|
||||
guidance_url:
|
||||
'https://www.notion.so/Creator-Communication-Guide-1b65ee711bf080ec9337e3ccdded146c',
|
||||
navigate: '/moderation',
|
||||
actions: [
|
||||
{
|
||||
id: 'rule_breaking_yes',
|
||||
type: 'button',
|
||||
label: 'Yes',
|
||||
weight: 0,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'critical',
|
||||
message: async () => (await import('../messages/rule-breaking.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Please explain to the user how it infringes on our content rules.',
|
||||
variable: 'MESSAGE',
|
||||
required: true,
|
||||
large: true,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
title: 'Does this project violate the rules?',
|
||||
id: 'rule-following',
|
||||
icon: ListBulletedIcon,
|
||||
guidance_url:
|
||||
'https://www.notion.so/Creator-Communication-Guide-1b65ee711bf080ec9337e3ccdded146c',
|
||||
navigate: '/moderation',
|
||||
actions: [
|
||||
{
|
||||
id: 'rule_breaking_yes',
|
||||
type: 'button',
|
||||
label: 'Yes',
|
||||
weight: 0,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'critical',
|
||||
message: async () => (await import('../messages/rule-breaking.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Please explain to the user how it infringes on our content rules.',
|
||||
variable: 'MESSAGE',
|
||||
required: true,
|
||||
large: true,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default ruleFollowing
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { GlobeIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const sideTypes: Stage = {
|
||||
title: "Is the project's environment information accurate?",
|
||||
id: 'environment',
|
||||
icon: GlobeIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings',
|
||||
text: async () => (await import('../messages/checklist-text/side_types.md?raw')).default,
|
||||
actions: [
|
||||
{
|
||||
id: 'side_types_inaccurate_modpack',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 800,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/side-types/inaccurate-modpack.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'side_types_inaccurate_mod',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 800,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'mod',
|
||||
message: async () => (await import('../messages/side-types/inaccurate-mod.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
title: "Is the project's environment information accurate?",
|
||||
id: 'environment',
|
||||
icon: GlobeIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings',
|
||||
text: async () => (await import('../messages/checklist-text/side_types.md?raw')).default,
|
||||
actions: [
|
||||
{
|
||||
id: 'side_types_inaccurate_modpack',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 800,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/side-types/inaccurate-modpack.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'side_types_inaccurate_mod',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 800,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'mod',
|
||||
message: async () => (await import('../messages/side-types/inaccurate-mod.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default sideTypes
|
||||
|
||||
@@ -1,88 +1,89 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
|
||||
import { TriangleAlertIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const statusAlerts: Stage = {
|
||||
title: `Is anything else affecting this project's status?`,
|
||||
id: 'status-alerts',
|
||||
icon: TriangleAlertIcon,
|
||||
text: async () => (await import('../messages/checklist-text/status-alerts/text.md?raw')).default,
|
||||
guidance_url:
|
||||
'https://www.notion.so/Project-Modification-Guidelines-22e5ee711bf080628416f0471ba6af02',
|
||||
navigate: '/moderation',
|
||||
actions: [
|
||||
{
|
||||
id: 'status_corrections_applied',
|
||||
type: 'button',
|
||||
label: 'Corrections applied',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'approved',
|
||||
disablesActions: ['status_private_use', 'status_account_issues'],
|
||||
message: async () => (await import('../messages/status-alerts/fixed.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_private_use',
|
||||
type: 'button',
|
||||
label: 'Private use',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'flagged',
|
||||
disablesActions: ['status_corrections_applied', 'status_account_issues'],
|
||||
message: async () => (await import('../messages/status-alerts/private.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_account_issues',
|
||||
type: 'button',
|
||||
label: 'Account issues',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'rejected',
|
||||
disablesActions: ['status_corrections_applied', 'status_private_use'],
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/account_issues.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_tec_source_request',
|
||||
type: 'button',
|
||||
label: `Request Source`,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'critical',
|
||||
disablesActions: ['status_corrections_applied', 'status_private_use'],
|
||||
shouldShow: (project) =>
|
||||
project.project_type === 'mod' ||
|
||||
project.project_type === 'shader' ||
|
||||
project.project_type.toString() === 'plugin',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'status_tec_source_request_options',
|
||||
type: 'dropdown',
|
||||
label: 'Why are you requesting source?',
|
||||
options: [
|
||||
{
|
||||
label: 'Obfuscated',
|
||||
weight: 999999,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/tec/source_request-obfs.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Binaries',
|
||||
weight: 999000,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/tec/source_request-bins.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_automod_confusion',
|
||||
type: 'button',
|
||||
label: `Automod confusion`,
|
||||
weight: -999999,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/automod_confusion.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
title: `Is anything else affecting this project's status?`,
|
||||
id: 'status-alerts',
|
||||
icon: TriangleAlertIcon,
|
||||
text: async () => (await import('../messages/checklist-text/status-alerts/text.md?raw')).default,
|
||||
guidance_url:
|
||||
'https://www.notion.so/Project-Modification-Guidelines-22e5ee711bf080628416f0471ba6af02',
|
||||
navigate: '/moderation',
|
||||
actions: [
|
||||
{
|
||||
id: 'status_corrections_applied',
|
||||
type: 'button',
|
||||
label: 'Corrections applied',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'approved',
|
||||
disablesActions: ['status_private_use', 'status_account_issues'],
|
||||
message: async () => (await import('../messages/status-alerts/fixed.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_private_use',
|
||||
type: 'button',
|
||||
label: 'Private use',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'flagged',
|
||||
disablesActions: ['status_corrections_applied', 'status_account_issues'],
|
||||
message: async () => (await import('../messages/status-alerts/private.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_account_issues',
|
||||
type: 'button',
|
||||
label: 'Account issues',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'rejected',
|
||||
disablesActions: ['status_corrections_applied', 'status_private_use'],
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/account_issues.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_tec_source_request',
|
||||
type: 'button',
|
||||
label: `Request Source`,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'critical',
|
||||
disablesActions: ['status_corrections_applied', 'status_private_use'],
|
||||
shouldShow: (project) =>
|
||||
project.project_type === 'mod' ||
|
||||
project.project_type === 'shader' ||
|
||||
project.project_type.toString() === 'plugin',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'status_tec_source_request_options',
|
||||
type: 'dropdown',
|
||||
label: 'Why are you requesting source?',
|
||||
options: [
|
||||
{
|
||||
label: 'Obfuscated',
|
||||
weight: 999999,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/tec/source_request-obfs.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Binaries',
|
||||
weight: 999000,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/tec/source_request-bins.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_automod_confusion',
|
||||
type: 'button',
|
||||
label: `Automod confusion`,
|
||||
weight: -999999,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/automod_confusion.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default statusAlerts
|
||||
|
||||
@@ -1,53 +1,54 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { AlignLeftIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const summary: Stage = {
|
||||
title: "Is the project's summary sufficient?",
|
||||
text: async () => (await import('../messages/checklist-text/summary/summary.md?raw')).default,
|
||||
id: 'summary',
|
||||
icon: AlignLeftIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
actions: [
|
||||
{
|
||||
id: 'summary_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 300,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
disablesActions: ['summary_repeat_title'],
|
||||
message: async () => (await import('../messages/summary/insufficient.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_repeat_title',
|
||||
type: 'button',
|
||||
label: 'Repeat of title',
|
||||
weight: 300,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
disablesActions: ['summary_insufficient'],
|
||||
message: async () => (await import('../messages/summary/repeat-title.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_formatting',
|
||||
type: 'button',
|
||||
label: 'Formatting',
|
||||
weight: 301,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/summary/formatting.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_non_english',
|
||||
type: 'button',
|
||||
label: 'Non-english',
|
||||
weight: 302,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/summary/non-english.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
title: "Is the project's summary sufficient?",
|
||||
text: async () => (await import('../messages/checklist-text/summary/summary.md?raw')).default,
|
||||
id: 'summary',
|
||||
icon: AlignLeftIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
actions: [
|
||||
{
|
||||
id: 'summary_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 300,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
disablesActions: ['summary_repeat_title'],
|
||||
message: async () => (await import('../messages/summary/insufficient.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_repeat_title',
|
||||
type: 'button',
|
||||
label: 'Repeat of title',
|
||||
weight: 300,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
disablesActions: ['summary_insufficient'],
|
||||
message: async () => (await import('../messages/summary/repeat-title.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_formatting',
|
||||
type: 'button',
|
||||
label: 'Formatting',
|
||||
weight: 301,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/summary/formatting.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_non_english',
|
||||
type: 'button',
|
||||
label: 'Non-english',
|
||||
weight: 302,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/summary/non-english.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default summary
|
||||
|
||||
@@ -1,96 +1,97 @@
|
||||
import { BookOpenIcon } from '@modrinth/assets'
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { Project } from '@modrinth/utils'
|
||||
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
function hasCustomSlug(project: Project): boolean {
|
||||
return (
|
||||
project.slug !==
|
||||
project.title
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '-')
|
||||
.replaceAll(/[^a-zA-Z0-9!@$()`.+,_"-]/g, '')
|
||||
.replaceAll(/--+/gm, '-')
|
||||
)
|
||||
return (
|
||||
project.slug !==
|
||||
project.title
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '-')
|
||||
.replaceAll(/[^a-zA-Z0-9!@$()`.+,_"-]/g, '')
|
||||
.replaceAll(/--+/gm, '-')
|
||||
)
|
||||
}
|
||||
|
||||
const titleSlug: Stage = {
|
||||
title: 'Are the Name and URL accurate and appropriate?',
|
||||
id: 'title-&-slug',
|
||||
text: async (project) => {
|
||||
let text = (await import('../messages/checklist-text/title-slug/title.md?raw')).default
|
||||
if (hasCustomSlug(project))
|
||||
text += (await import('../messages/checklist-text/title-slug/slug.md?raw')).default
|
||||
return text
|
||||
},
|
||||
icon: BookOpenIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
actions: [
|
||||
{
|
||||
id: 'title_useless_info',
|
||||
type: 'button',
|
||||
label: 'Contains useless info',
|
||||
weight: 100,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/title/useless-info.md?raw')).default,
|
||||
},
|
||||
{
|
||||
id: 'title_minecraft_branding',
|
||||
type: 'button',
|
||||
label: 'Minecraft title',
|
||||
weight: 100,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/title/minecraft-branding.md?raw')).default,
|
||||
},
|
||||
{
|
||||
id: 'title_similarities',
|
||||
type: 'button',
|
||||
label: 'Title similarities',
|
||||
weight: 110,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/title/similarities.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'title_similarities_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Similarities additional info',
|
||||
options: [
|
||||
{
|
||||
label: 'Modpack named after mod',
|
||||
weight: 111,
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/title/similarities-modpack.md?raw')).default,
|
||||
},
|
||||
{
|
||||
label: 'Forked project',
|
||||
weight: 112,
|
||||
message: async () =>
|
||||
(await import('../messages/title/similarities-fork.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'slug_misused_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Slug issues?',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => hasCustomSlug(project),
|
||||
options: [
|
||||
{
|
||||
label: 'Misused',
|
||||
weight: 200,
|
||||
message: async () => (await import('../messages/slug/misused.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
title: 'Are the Name and URL accurate and appropriate?',
|
||||
id: 'title-&-slug',
|
||||
text: async (project) => {
|
||||
let text = (await import('../messages/checklist-text/title-slug/title.md?raw')).default
|
||||
if (hasCustomSlug(project))
|
||||
text += (await import('../messages/checklist-text/title-slug/slug.md?raw')).default
|
||||
return text
|
||||
},
|
||||
icon: BookOpenIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
actions: [
|
||||
{
|
||||
id: 'title_useless_info',
|
||||
type: 'button',
|
||||
label: 'Contains useless info',
|
||||
weight: 100,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/title/useless-info.md?raw')).default,
|
||||
},
|
||||
{
|
||||
id: 'title_minecraft_branding',
|
||||
type: 'button',
|
||||
label: 'Minecraft title',
|
||||
weight: 100,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/title/minecraft-branding.md?raw')).default,
|
||||
},
|
||||
{
|
||||
id: 'title_similarities',
|
||||
type: 'button',
|
||||
label: 'Title similarities',
|
||||
weight: 110,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/title/similarities.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'title_similarities_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Similarities additional info',
|
||||
options: [
|
||||
{
|
||||
label: 'Modpack named after mod',
|
||||
weight: 111,
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/title/similarities-modpack.md?raw')).default,
|
||||
},
|
||||
{
|
||||
label: 'Forked project',
|
||||
weight: 112,
|
||||
message: async () =>
|
||||
(await import('../messages/title/similarities-fork.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'slug_misused_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Slug issues?',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => hasCustomSlug(project),
|
||||
options: [
|
||||
{
|
||||
label: 'Misused',
|
||||
weight: 200,
|
||||
message: async () => (await import('../messages/slug/misused.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default titleSlug
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const undefinedProjectStage: Stage = {
|
||||
title: 'This project is undefined!',
|
||||
id: 'undefined-project',
|
||||
icon: XIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/versions',
|
||||
shouldShow: (project) => project.versions.length === 0,
|
||||
actions: [
|
||||
{
|
||||
id: 'undefined_no_versions',
|
||||
type: 'button',
|
||||
label: 'No Versions',
|
||||
weight: -100,
|
||||
suggestedStatus: 'rejected',
|
||||
message: async () =>
|
||||
(await import('../messages/undefined-project/no_versions.md?raw')).default,
|
||||
},
|
||||
],
|
||||
title: 'This project is undefined!',
|
||||
id: 'undefined-project',
|
||||
icon: XIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/versions',
|
||||
shouldShow: (project) => project.versions.length === 0,
|
||||
actions: [
|
||||
{
|
||||
id: 'undefined_no_versions',
|
||||
type: 'button',
|
||||
label: 'No Versions',
|
||||
weight: -100,
|
||||
suggestedStatus: 'rejected',
|
||||
message: async () =>
|
||||
(await import('../messages/undefined-project/no_versions.md?raw')).default,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default undefinedProjectStage
|
||||
|
||||
@@ -1,174 +1,175 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
|
||||
import { VersionIcon } from '@modrinth/assets'
|
||||
|
||||
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const versions: Stage = {
|
||||
title: "Are this project's files correct?",
|
||||
id: 'versions',
|
||||
icon: VersionIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/versions',
|
||||
actions: [
|
||||
{
|
||||
id: 'versions_incorrect_additional',
|
||||
type: 'button',
|
||||
label: 'Incorrect additional files',
|
||||
weight: 1000,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/incorrect_additional_files.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_incorrect_project_type',
|
||||
type: 'button',
|
||||
label: 'Incorrect Project Type',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'medium',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'versions_incorrect_project_type_options',
|
||||
type: 'dropdown',
|
||||
label: 'What type should this project be?',
|
||||
options: [
|
||||
{
|
||||
label: 'Modpack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => project.project_type !== 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-modpacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Resource Pack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => project.project_type !== 'resourcepack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-resourcepacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Data Pack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => !project.loaders.includes('datapack'),
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-datapacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_alternate_versions',
|
||||
type: 'button',
|
||||
label: 'Alternate Versions',
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'versions_alternate_versions_options',
|
||||
type: 'dropdown',
|
||||
label: 'How are the alternate versions distributed?',
|
||||
options: [
|
||||
{
|
||||
label: 'Primary Files',
|
||||
weight: 1002,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-primary.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Additional Files',
|
||||
weight: 1002,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-additional.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Monofile',
|
||||
weight: 1002,
|
||||
shouldShow: (project) =>
|
||||
project.project_type === 'resourcepack' || project.loaders.includes('datapack'),
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-mono.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Server Files (Primary Files)',
|
||||
weight: 1002,
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-server.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Server Files (Additional Files)',
|
||||
weight: 1002,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-server-additional.md?raw'))
|
||||
.default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'mods.zip',
|
||||
weight: 1002,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-zip.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_vanilla_assets',
|
||||
type: 'button',
|
||||
label: 'Vanilla Assets',
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1003,
|
||||
shouldShow: (project) => project.project_type === 'resourcepack',
|
||||
message: async () => (await import('../messages/versions/vanilla_assets.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_redist_libs',
|
||||
type: 'button',
|
||||
label: 'Packed Libs',
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1003,
|
||||
shouldShow: (project) => project.project_type === 'mod' || project.project_type === 'plugin',
|
||||
message: async () => (await import('../messages/versions/redist_libs.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_duplicate_primary_files',
|
||||
type: 'button',
|
||||
label: 'Duplicate Primary Files',
|
||||
suggestedStatus: 'flagged',
|
||||
severity: `medium`,
|
||||
weight: 1004,
|
||||
message: async () => (await import('../messages/versions/broken_version.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'unsupported_project_type',
|
||||
type: 'button',
|
||||
label: `Unsupported`,
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1005,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/unsupported_project.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Project Type',
|
||||
required: true,
|
||||
variable: 'INVALID_TYPE',
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
title: "Are this project's files correct?",
|
||||
id: 'versions',
|
||||
icon: VersionIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/versions',
|
||||
actions: [
|
||||
{
|
||||
id: 'versions_incorrect_additional',
|
||||
type: 'button',
|
||||
label: 'Incorrect additional files',
|
||||
weight: 1000,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/incorrect_additional_files.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_incorrect_project_type',
|
||||
type: 'button',
|
||||
label: 'Incorrect Project Type',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'medium',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'versions_incorrect_project_type_options',
|
||||
type: 'dropdown',
|
||||
label: 'What type should this project be?',
|
||||
options: [
|
||||
{
|
||||
label: 'Modpack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => project.project_type !== 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-modpacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Resource Pack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => project.project_type !== 'resourcepack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-resourcepacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Data Pack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => !project.loaders.includes('datapack'),
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-datapacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_alternate_versions',
|
||||
type: 'button',
|
||||
label: 'Alternate Versions',
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'versions_alternate_versions_options',
|
||||
type: 'dropdown',
|
||||
label: 'How are the alternate versions distributed?',
|
||||
options: [
|
||||
{
|
||||
label: 'Primary Files',
|
||||
weight: 1002,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-primary.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Additional Files',
|
||||
weight: 1002,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-additional.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Monofile',
|
||||
weight: 1002,
|
||||
shouldShow: (project) =>
|
||||
project.project_type === 'resourcepack' || project.loaders.includes('datapack'),
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-mono.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Server Files (Primary Files)',
|
||||
weight: 1002,
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-server.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Server Files (Additional Files)',
|
||||
weight: 1002,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-server-additional.md?raw'))
|
||||
.default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'mods.zip',
|
||||
weight: 1002,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-zip.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_vanilla_assets',
|
||||
type: 'button',
|
||||
label: 'Vanilla Assets',
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1003,
|
||||
shouldShow: (project) => project.project_type === 'resourcepack',
|
||||
message: async () => (await import('../messages/versions/vanilla_assets.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_redist_libs',
|
||||
type: 'button',
|
||||
label: 'Packed Libs',
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1003,
|
||||
shouldShow: (project) => project.project_type === 'mod' || project.project_type === 'plugin',
|
||||
message: async () => (await import('../messages/versions/redist_libs.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_duplicate_primary_files',
|
||||
type: 'button',
|
||||
label: 'Duplicate Primary Files',
|
||||
suggestedStatus: 'flagged',
|
||||
severity: `medium`,
|
||||
weight: 1004,
|
||||
message: async () => (await import('../messages/versions/broken_version.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'unsupported_project_type',
|
||||
type: 'button',
|
||||
label: `Unsupported`,
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1005,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/unsupported_project.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Project Type',
|
||||
required: true,
|
||||
variable: 'INVALID_TYPE',
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default versions
|
||||
|
||||
Reference in New Issue
Block a user