+
Title: {project.value.name}
+
+
Slug: {project.value.slug}
+
+
Slug Taken:{" "}
+ {autoSlugStatus.value === "loading" ? (
+ "..."
+ ) : by ? (
+
+ ) : (
+ "No"
+ )}
+
+ )
+ },
- return stage('title-slug', 'Title & Slug')
- .hint('Are the Name and URL accurate and appropriate?')
- .guidance(
- 'https://www.notion.so/2e15ee711bf080e4a41df61bbab49892#2e15ee711bf0803c9660e90f0fead705',
- )
- .icon(BookOpenIcon)
- .children(
- () => {
- if (!hasCustomSlug(project.value)) {
- return (
-
-
Title: {project.value.name}
-
-
Slug: {project.value.slug}
-
-
Slug Taken:{' '}
- {autoSlugStatus.value === 'loading' ? (
- '...'
- ) : by ? (
- <>
-
- {by.name}
-
- {` (${by.status})`}
- >
- ) : (
- 'No'
- )}
-
- )
- },
+ group("title")
+ .title("Title Issues?")
+ .children(
+ toggle("useless-info", "Contains Useless Info").suggestedStatus("flagged").message(),
- group('title')
- .title('Title Issues?')
- .children(
- toggle('useless-info', 'Contains Useless Info')
- .suggestedStatus('flagged')
- .severity('low')
- .message(),
+ toggle("minecraft-branding", "Minecraft Title").suggestedStatus("flagged").message(),
- toggle('minecraft-branding', 'Minecraft Title')
- .suggestedStatus('flagged')
- .severity('medium')
- .message(),
+ toggle("similarities", "Title Similarities")
+ .suggestedStatus("flagged")
+ .message()
+ .children(
+ group()
+ .title("Similarities Additional Info")
+ .children(
+ check("modpack", "Modpack Named After Mod")
+ .shown(computed(() => project.value.project_types.includes("modpack")))
+ .message(),
- toggle('similarities', 'Title Similarities')
- .suggestedStatus('flagged')
- .severity('medium')
- .message()
- .children(
- group()
- .title('Similarities Additional Info')
- .multiSelect('options')
- .children(
- option('modpack', 'Modpack Named After Mod')
- .shown(computed(() => project.value.project_types.includes('modpack')))
- .message(),
+ check("fork", "Forked Project")
+ .shown(computed(() => !project.value?.minecraft_server))
+ .message(),
+ ),
+ )
+ .collect(),
+ ),
- option('fork', 'Forked Project')
- .shown(computed(() => !project.value?.minecraft_server))
- .message(),
- ),
- )
- .collect(),
- ),
+ group("slug")
+ .title("Slug Issues")
+ .shown(computed(() => hasCustomSlug(project.value)))
+ .children(
+ group().children(
+ toggle("misused", "Misused")
+ .children(
+ group()
+ .title("Correct Slug")
+ .children(
+ text("correct-slug")
+ .initial(() => resolvedAutoSlug.value ?? project.value.slug ?? "")
+ .onChange((value) => {
+ clearTimeout(slugDebounceTimer)
+ if (value === project.value.slug) {
+ slugValidation.value = "unchanged"
+ return
+ }
+ if (!value) {
+ slugValidation.value = "empty"
+ correctSlugConflict.value = null
+ return
+ }
+ if (generateUrlSlug(value) !== value) {
+ slugValidation.value = "invalid"
+ correctSlugConflict.value = null
+ return
+ }
+ slugValidation.value = "checking"
+ slugDebounceTimer = setTimeout(async () => {
+ const conflict = await checkSlugTaken(value).catch(() => null)
+ if (conflict !== null && conflict.id !== project.value.id) {
+ correctSlugConflict.value = conflict
+ slugValidation.value = "taken"
+ } else {
+ correctSlugConflict.value = null
+ slugValidation.value = "available"
+ }
+ }, 400)
+ })
+ .tweak(TagCategoryWandSparklesIcon, () => autoSlugStatus.value === "available" ? resolvedAutoSlug.value : null)
+ .tweak(UserPlusIcon, (current) => ownerUsername.value && !current?.includes(ownerUsername.value) ? `${current}-${ownerUsername.value}` : null)
+ .tweak(WrenchIcon, (current) => generateUrlSlug(current ?? ""))
+ .tweak(TagCategoryRefreshCcwIcon, () => project.value.slug),
- group('slug')
- .title('Slug Issues')
- .shown(computed(() => hasCustomSlug(project.value)))
- .children(
- group()
- .multiSelect('issues')
- .children(
- toggle('misused', 'Misused')
- .children(
- group()
- .title('Correct Slug')
- .children(
- text('correct-slug')
- .initial(() => resolvedAutoSlug.value ?? project.value.slug)
- .onChange((value, { override }) => {
- if (!value) return override(project.value.slug ?? '')
- clearTimeout(slugDebounceTimer)
- if (value === project.value.slug) {
- slugValidation.value = 'unchanged'
- return
- }
- slugValidation.value = 'checking'
- slugDebounceTimer = setTimeout(async () => {
- const conflict = await checkSlugTaken(value).catch(() => null)
- if (conflict !== null && conflict.id !== project.value.id) {
- correctSlugConflict.value = conflict
- slugValidation.value = 'taken'
- } else {
- correctSlugConflict.value = null
- slugValidation.value = 'available'
- }
- }, 400)
- }),
-
- button()
- .icon(TagCategoryWandSparklesIcon)
- .tooltip(computed(() => resolvedAutoSlug.value ?? ''))
- .enabled(
- (state) =>
- autoSlugStatus.value === 'available' &&
- resolvedAutoSlug.value !== null &&
- resolvedAutoSlug.value !== currentSlug(state),
- )
- .onClick((state) => {
- if (resolvedAutoSlug.value) state['correct-slug'] = resolvedAutoSlug.value
- }),
-
- button()
- .icon(UserPlusIcon)
- .tooltip((state) => {
- const current = currentSlug(state)
- if (!ownerUsername.value || current?.includes(ownerUsername.value))
- return current ?? ''
- return `${current}-${ownerUsername.value}`
- })
- .enabled(
- (state) =>
- ownerUsername.value !== null &&
- !currentSlug(state)?.includes(ownerUsername.value),
- )
- .onClick((state) => {
- state['correct-slug'] = `${currentSlug(state)}-${ownerUsername.value}`
- }),
-
- button()
- .icon(TagCategoryRefreshCcwIcon)
- .tooltip(computed(() => project.value.slug ?? ''))
- .enabled((state) => currentSlug(state) !== project.value.slug)
- .onClick((state) => {
- state['correct-slug'] = project.value.slug
- }),
-
- SlugStatus,
- ),
- )
- .rawMessage(async (state) => {
- let correct = ''
- if (slugValidation.value === 'available') {
- const slug = state['correct-slug'] as string | undefined
- if (slug)
- correct = await md('checklist/messages/title-slug/slug/correction', () => ({
- SUGGESTED_SLUG: slug,
- }))(state)
- }
- return md('checklist/messages/title-slug/slug/misused', () => ({
- CORRECT: correct,
- }))(state)
- })
- .fix(
- //TODO chyz think of some way to have initial values actually be reflected in state without having to store them
- fix().project((patch, state) => {
- const slug =
- (state['correct-slug'] as string | undefined) ?? resolvedAutoSlug.value
- if (!slug || slug === project.value.slug) return
- patch.slug = slug
- }),
- ),
- ),
- ),
- )
+ SlugStatus,
+ ),
+ )
+ .rawMessage(async (state) => {
+ let correct = ""
+ if (slugValidation.value === "available") {
+ const slug = state["correct-slug"] as string | undefined
+ if (slug)
+ correct = await md("checklist/messages/title-slug/slug/correction", () => ({
+ SUGGESTED_SLUG: slug,
+ }))(state)
+ }
+ return md("checklist/messages/title-slug/slug/misused", () => ({
+ CORRECT: correct,
+ }))(state)
+ })
+ .fix(
+ fix().project((patch, state) => {
+ if (slugValidation.value !== "available") return
+ patch.slug = state["correct-slug"] as string
+ }),
+ ),
+ ),
+ ),
+ )
}
diff --git a/packages/moderation/src/data/stages/versions.tsx b/packages/moderation/src/data/stages/versions.tsx
index 3e18ded1f5..47a273cc25 100644
--- a/packages/moderation/src/data/stages/versions.tsx
+++ b/packages/moderation/src/data/stages/versions.tsx
@@ -19,38 +19,31 @@ export default function () {
group().children(
toggle('incorrect-additional-files', 'Incorrect additional files')
.suggestedStatus('flagged')
- .severity('medium')
.message(),
toggle('incorrect-project-type', 'Incorrect Project Type')
.suggestedStatus('rejected')
- .severity('medium')
.children(
dropdown('type')
.title('Correct Project Type')
+ .required()
.none('Unknown')
- .children(
- option('modpack', 'Modpack')
- .shown(computed(() => !project.value.project_types.includes('modpack')))
- .message(),
- option('resourcepack', 'Resource Pack')
- .shown(computed(() => !project.value.project_types.includes('resourcepack')))
- .message(),
- option('datapack', 'Data Pack')
- .shown(computed(() => !project.value.loaders.includes('datapack')))
- .message(),
+ .options(
+ option('modpack', 'Modpack').message(),
+ option('resourcepack', 'Resource Pack').message(),
+ option('datapack', 'Data Pack').message(),
),
)
.collect(),
toggle('alternate-versions', 'Alternate Versions')
.suggestedStatus('rejected')
- .severity('high')
.children(
dropdown('distribution')
.title('Distribution Type')
+ .required()
.none('Unknown')
- .children(
+ .options(
option('primary', 'Primary Files').message(),
option('additional', 'Additional Files').message(),
option('mono', 'Monofile')
@@ -78,7 +71,6 @@ export default function () {
toggle('vanilla-assets', 'Vanilla Assets')
.shown(computed(() => project.value.project_types.includes('resourcepack')))
.suggestedStatus('rejected')
- .severity('medium')
.message(),
toggle('redist-libs', 'Packed Libs')
@@ -90,18 +82,15 @@ export default function () {
),
)
.suggestedStatus('rejected')
- .severity('medium')
.message(),
toggle('duplicate-primary-files', 'Duplicate Primary Files')
.suggestedStatus('flagged')
- .severity('medium')
.message(),
toggle('unsupported', 'Unsupported')
.suggestedStatus('rejected')
- .severity('medium')
- .message(undefined, (state) => ({
+ .message((state) => ({
INVALID_TYPE: state['invalid-type'],
}))
.children(text('invalid-type').title('Unsupported Type').required()),
diff --git a/packages/moderation/src/index.ts b/packages/moderation/src/index.ts
index 95d7944884..ae3041d724 100644
--- a/packages/moderation/src/index.ts
+++ b/packages/moderation/src/index.ts
@@ -1,4 +1,4 @@
-export { default as checklist, stages, useStages } from './data/checklist'
+export { useStages } from './data/checklist'
export { default as keybinds } from './data/keybinds'
export { default as moderationSettings } from './data/settings'
export { default as nags } from './data/nags'
@@ -10,16 +10,12 @@ export {
default as techReviewQuickReplies,
} from './data/quick-replies/tech-review-quick-replies'
export * from './locales'
-export * from './types/actions'
export * from './types/keybinds'
export * from './types/settings'
-export * from './types/messages'
export * from './types/nags'
-export * from './types/node'
export * from './types/priority'
export * from './types/quick-reply'
export * from './types/reports'
-export * from './types/stage'
export * from './utils'
export * from './handles/keybinds'
export * from './handles/settings'
diff --git a/packages/moderation/src/types/actions.ts b/packages/moderation/src/types/actions.ts
deleted file mode 100644
index 3332dabc61..0000000000
--- a/packages/moderation/src/types/actions.ts
+++ /dev/null
@@ -1,298 +0,0 @@
-import type { Labrinth } from '@modrinth/api-client'
-
-import type { WeightedMessage } from './messages'
-
-export type ActionType =
- | 'button'
- | 'dropdown'
- | 'multi-select-chips'
- | 'toggle'
- | 'conditional-button'
-
-export type Action =
- | ButtonAction
- | DropdownAction
- | MultiSelectChipsAction
- | ToggleAction
- | ConditionalButtonAction
-
-export type ModerationStatus = 'approved' | 'rejected' | 'flagged'
-export type ModerationSeverity = 'low' | 'medium' | 'high' | 'critical'
-
-export interface ChecklistActionContext {
- project: Labrinth.Projects.v2.Project
- projectV3: Labrinth.Projects.v3.Project
- versions?: Labrinth.Versions.v2.Version[] | null
-}
-
-export interface BaseAction {
- /**
- * The type of action, which determines how the action is presented to the moderator and what it does.
- */
- type: ActionType
-
- /**
- * Any additional text data that is required to complete the action.
- */
- relevantExtraInput?: AdditionalTextInput[]
-
- /**
- * Suggested moderation status when this action is selected.
- */
- suggestedStatus?: ModerationStatus
-
- /**
- * Suggested severity level for this moderation action.
- */
- severity?: ModerationSeverity
-
- /**
- * Actions that become available when this action is selected.
- */
- enablesActions?: Action[]
-
- /**
- * Actions that become unavailable when this action is selected.
- */
- disablesActions?: string[] // Array of action IDs
-
- /**
- * Unique identifier for this action, used for conditional logic.
- */
- id?: string
-
- /**
- * A function that determines whether this action should be shown for a given project.
- *
- * By default, it returns `true`, meaning the action is always shown.
- */
- shouldShow?: (
- project: Labrinth.Projects.v2.Project,
- projectV3: Labrinth.Projects.v3.Project,
- context?: ChecklistActionContext,
- ) => boolean
-}
-
-/**
- * Represents a conditional message that changes based on other selected actions.
- */
-export interface ConditionalMessage extends WeightedMessage {
- /**
- * Conditions that must be met for this message to be used.
- */
- conditions: {
- /**
- * Action IDs that must be selected for this message to apply.
- */
- requiredActions?: string[]
-
- /**
- * Action IDs that must NOT be selected for this message to apply.
- */
- excludedActions?: string[]
- }
-}
-
-/**
- * Represents a button action, which is a simple toggle button that can be used to append a message to the final moderation message.
- */
-export interface ButtonAction extends BaseAction, WeightedMessage {
- type: 'button'
-
- /**
- * The label of the button, which is displayed to the moderator. The text on the button.
- */
- label: string
-
- /**
- * Alternative messages based on other selected actions.
- */
- conditionalMessages?: ConditionalMessage[]
-}
-
-/**
- * Represents a simple toggle/checkbox action with separate layout handling.
- */
-export interface ToggleAction extends BaseAction, WeightedMessage {
- type: 'toggle'
-
- /**
- * The label of the toggle, which is displayed to the moderator.
- */
- label: string
-
- /**
- * Description text that appears below the toggle.
- */
- description?: string
-
- /**
- * Whether the toggle is checked by default.
- */
- defaultChecked?: boolean
-
- /**
- * Alternative messages based on other selected actions.
- */
- conditionalMessages?: ConditionalMessage[]
-}
-
-/**
- * Represents a button that has different behavior based on other selected actions.
- */
-export interface ConditionalButtonAction extends BaseAction {
- type: 'conditional-button'
-
- /**
- * The label of the button, which is displayed to the moderator.
- */
- label: string
-
- /**
- * Different message configurations based on conditions.
- */
- messageVariants: ConditionalMessage[]
-
- /**
- * Global fallback message if no variants match their conditions.
- */
- fallbackMessage?: () => Promise