From 934936eba886b10332433345a123b9b6ff79ffd7 Mon Sep 17 00:00:00 2001 From: Prospector <6166773+Prospector@users.noreply.github.com> Date: Mon, 22 Dec 2025 12:48:17 -0800 Subject: [PATCH 001/158] changelog --- packages/assets/generated-icons.ts | 2 +- packages/utils/changelog.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/assets/generated-icons.ts b/packages/assets/generated-icons.ts index 060333fe1..7d5df72c0 100644 --- a/packages/assets/generated-icons.ts +++ b/packages/assets/generated-icons.ts @@ -305,8 +305,8 @@ export const FilterIcon = _FilterIcon export const FolderArchiveIcon = _FolderArchiveIcon export const FolderOpenIcon = _FolderOpenIcon export const FolderSearchIcon = _FolderSearchIcon -export const FolderIcon = _FolderIcon export const FolderUpIcon = _FolderUpIcon +export const FolderIcon = _FolderIcon export const GameIcon = _GameIcon export const GapIcon = _GapIcon export const GaugeIcon = _GaugeIcon diff --git a/packages/utils/changelog.ts b/packages/utils/changelog.ts index 74eb943f6..aec40a1bb 100644 --- a/packages/utils/changelog.ts +++ b/packages/utils/changelog.ts @@ -10,6 +10,13 @@ export type VersionEntry = { } const VERSIONS: VersionEntry[] = [ + { + date: `2025-12-22T12:55:00-08:00`, + product: 'app', + version: '0.10.24', + body: `## Improvements +- Fixed issue with modpack export not working with certain projects.`, + }, { date: `2025-12-19T13:45:00-08:00`, product: 'web', From 88635d8da8b44d802e126d40f97be6b6a4d518f8 Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Mon, 22 Dec 2025 22:03:44 +0000 Subject: [PATCH 002/158] fix: auto-icon utility import (#4950) --- .../ui/dashboard/withdraw-stages/MuralpayDetailsStage.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/src/components/ui/dashboard/withdraw-stages/MuralpayDetailsStage.vue b/apps/frontend/src/components/ui/dashboard/withdraw-stages/MuralpayDetailsStage.vue index b7447ee45..e6422042d 100644 --- a/apps/frontend/src/components/ui/dashboard/withdraw-stages/MuralpayDetailsStage.vue +++ b/apps/frontend/src/components/ui/dashboard/withdraw-stages/MuralpayDetailsStage.vue @@ -209,6 +209,8 @@ import { formFieldPlaceholders, getBlockchainColor, getBlockchainIcon, + getCurrencyColor, + getCurrencyIcon, normalizeChildren, } from '@modrinth/ui' import { defineMessages, useVIntl } from '@vintl/vintl' From 11a75e7657d15c7560bb2684dc06e4de4352e1f8 Mon Sep 17 00:00:00 2001 From: Prospector <6166773+Prospector@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:15:25 -0800 Subject: [PATCH 003/158] changelog --- packages/utils/changelog.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/utils/changelog.ts b/packages/utils/changelog.ts index aec40a1bb..99223b6fc 100644 --- a/packages/utils/changelog.ts +++ b/packages/utils/changelog.ts @@ -10,6 +10,12 @@ export type VersionEntry = { } const VERSIONS: VersionEntry[] = [ + { + date: `2025-12-22T14:20:00-08:00`, + product: 'web', + body: `## Improvements +- Fixed error when withdrawing in certain circumstances.`, + }, { date: `2025-12-22T12:55:00-08:00`, product: 'app', From 6a0bf5858e42cfe8aab96e6d80ae4157c7395fa3 Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Mon, 22 Dec 2025 22:45:13 +0000 Subject: [PATCH 004/158] fix: panel breaking with advancedDebugInfo (#4952) --- .../src/pages/hosting/manage/[id].vue | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/pages/hosting/manage/[id].vue b/apps/frontend/src/pages/hosting/manage/[id].vue index af1744649..caa8bc1d4 100644 --- a/apps/frontend/src/pages/hosting/manage/[id].vue +++ b/apps/frontend/src/pages/hosting/manage/[id].vue @@ -366,7 +366,7 @@ >

Server data

{{
-			JSON.stringify(server, null, ' ')
+			safeStringify(server)
 		}}
@@ -459,6 +459,24 @@ const errorTitle = ref('Error') const errorMessage = ref('An unexpected error occurred.') const errorLog = ref('') const errorLogFile = ref('') + +function safeStringify(obj: unknown, indent = ' '): string { + const seen = new WeakSet() + return JSON.stringify( + obj, + (_key, value) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) { + return '[Circular]' + } + seen.add(value) + } + return value + }, + indent, + ) +} + const serverData = computed(() => server.general) const isConnected = ref(false) const isWSAuthIncorrect = ref(false) From 543bd5acf7a6192fd464a84043497193bb5297f4 Mon Sep 17 00:00:00 2001 From: coolbot <76798835+coolbot100s@users.noreply.github.com> Date: Mon, 22 Dec 2025 15:37:44 -0800 Subject: [PATCH 005/158] Coolbot/moderation updates for versions changes (#4942) * update reports message to the correct support bubble color * update checklist to direct to new settings pages and use v3 env info * fix: project v2 + v3 in moderation checklist funcs * Split environment stage if project uses mixed environments. --------- Co-authored-by: Calum H. (IMB11) --- .../checklist/ModerationChecklist.vue | 93 +++++++++++-------- apps/frontend/src/pages/[type]/[id].vue | 1 - packages/moderation/src/data/checklist.ts | 6 +- .../environment/environment-multiple.md | 2 + .../environment.md} | 2 + .../data/messages/environment/inaccurate.md | 6 ++ .../src/data/messages/gallery/insufficient.md | 2 +- .../src/data/messages/gallery/not-relevant.md | 2 +- .../data/messages/reports/platform-issue.md | 2 +- .../messages/side-types/inaccurate-mod.md | 5 - .../messages/side-types/inaccurate-modpack.md | 5 - .../src/data/modpack-permissions-stage.ts | 5 +- packages/moderation/src/data/nags/tags.ts | 8 +- .../environment/environment-multiple.ts | 29 ++++++ .../data/stages/environment/environment.ts | 29 ++++++ .../moderation/src/data/stages/side-types.ts | 38 -------- .../moderation/src/data/stages/title-slug.ts | 4 +- packages/moderation/src/types/actions.ts | 8 +- packages/moderation/src/types/keybinds.ts | 4 +- packages/moderation/src/types/nags.ts | 5 +- packages/moderation/src/types/reports.ts | 5 +- packages/moderation/src/types/stage.ts | 12 ++- packages/moderation/src/utils.ts | 83 ++++++++++++----- 23 files changed, 216 insertions(+), 140 deletions(-) create mode 100644 packages/moderation/src/data/messages/checklist-text/environment/environment-multiple.md rename packages/moderation/src/data/messages/checklist-text/{side_types.md => environment/environment.md} (61%) create mode 100644 packages/moderation/src/data/messages/environment/inaccurate.md delete mode 100644 packages/moderation/src/data/messages/side-types/inaccurate-mod.md delete mode 100644 packages/moderation/src/data/messages/side-types/inaccurate-modpack.md create mode 100644 packages/moderation/src/data/stages/environment/environment-multiple.ts create mode 100644 packages/moderation/src/data/stages/environment/environment.ts delete mode 100644 packages/moderation/src/data/stages/side-types.ts diff --git a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue index 6d8135de8..d3dece98c 100644 --- a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue +++ b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue @@ -84,8 +84,8 @@
@@ -282,7 +282,7 @@ - @@ -360,7 +360,9 @@ import { expandVariables, finalPermissionMessages, findMatchingVariant, + flattenProjectV3Variables, flattenProjectVariables, + flattenStaticVariables, getActionIdForStage, getActionMessage, getVisibleInputs, @@ -380,6 +382,7 @@ import { Collapsible, DropdownSelect, injectNotificationManager, + injectProjectPageContext, MarkdownEditor, OverflowMenu, type OverflowMenuOption, @@ -387,7 +390,6 @@ import { import { type ModerationJudgements, type ModerationModpackItem, - type Project, type ProjectStatus, renderHighlightedString, } from '@modrinth/utils' @@ -404,14 +406,19 @@ const { addNotification } = notifications const keybindsModal = ref>() const props = defineProps<{ - project: Project collapsed: boolean }>() +const { projectV2, projectV3 } = injectProjectPageContext() + const moderationStore = useModerationStore() const variables = computed(() => { - return flattenProjectVariables(props.project) + return { + ...flattenStaticVariables(), + ...flattenProjectVariables(projectV2.value), + ...flattenProjectV3Variables(projectV3.value), + } }) const modpackPermissionsComplete = ref(false) @@ -445,12 +452,12 @@ function resetProgress() { message.value = '' loadingMessage.value = false - localStorage.removeItem(`modpack-permissions-${props.project.id}`) - localStorage.removeItem(`modpack-permissions-index-${props.project.id}`) + localStorage.removeItem(`modpack-permissions-${projectV2.value.id}`) + localStorage.removeItem(`modpack-permissions-index-${projectV2.value.id}`) - sessionStorage.removeItem(`modpack-permissions-data-${props.project.id}`) - sessionStorage.removeItem(`modpack-permissions-permanent-no-${props.project.id}`) - sessionStorage.removeItem(`modpack-permissions-updated-${props.project.id}`) + sessionStorage.removeItem(`modpack-permissions-data-${projectV2.value.id}`) + sessionStorage.removeItem(`modpack-permissions-permanent-no-${projectV2.value.id}`) + sessionStorage.removeItem(`modpack-permissions-updated-${projectV2.value.id}`) modpackPermissionsComplete.value = false modpackJudgements.value = {} @@ -468,7 +475,7 @@ function findFirstValidStage(): number { } const currentStageObj = computed(() => checklist[currentStage.value]) -const currentStage = useLocalStorage(`moderation-stage-${props.project.slug}`, () => +const currentStage = useLocalStorage(`moderation-stage-${projectV2.value.slug}`, () => findFirstValidStage(), ) @@ -477,7 +484,12 @@ const stageTextExpanded = computedAsync(async () => { const stage = checklist[stageIndex] if (stage.text) { return renderHighlightedString( - expandVariables(await stage.text(props.project), props.project, variables.value), + expandVariables( + await stage.text(projectV2.value, projectV3.value), + projectV2.value, + projectV3.value, + variables.value, + ), ) } return null @@ -489,7 +501,7 @@ interface ActionState { } const persistedActionStates = useLocalStorage( - `moderation-actions-${props.project.slug}`, + `moderation-actions-${projectV2.value.slug}`, {}, { serializer: { @@ -502,7 +514,7 @@ const persistedActionStates = useLocalStorage( const router = useRouter() const persistedTextInputs = useLocalStorage( - `moderation-inputs-${props.project.slug}`, + `moderation-inputs-${projectV2.value.slug}`, {} as Record, ) @@ -530,7 +542,7 @@ function handleKeybinds(event: KeyboardEvent) { handleKeybind( event, { - project: props.project, + project: projectV2.value, state: { currentStage: currentStage.value, totalStages: checklist.length, @@ -562,7 +574,7 @@ function handleKeybinds(event: KeyboardEvent) { tryResetProgress: resetProgress, tryExitModeration: () => emit('exit'), - tryApprove: () => sendMessage(props.project.requested_status), + tryApprove: () => sendMessage(projectV2.value.requested_status ?? 'approved'), tryReject: () => sendMessage('rejected'), tryWithhold: () => sendMessage('withheld'), tryEditMessage: goBackToStages, @@ -656,7 +668,7 @@ watch( (newIndex) => { const stage = checklist[newIndex] if (stage?.navigate) { - router.push(`/${props.project.project_type}/${props.project.slug}${stage.navigate}`) + router.push(`/${projectV2.value.project_type}/${projectV2.value.slug}${stage.navigate}`) } initializeCurrentStage() @@ -858,11 +870,11 @@ function getModpackFilesFromStorage(): { permanentNo: ModerationModpackItem[] } { try { - const sessionData = sessionStorage.getItem(`modpack-permissions-data-${props.project.id}`) + const sessionData = sessionStorage.getItem(`modpack-permissions-data-${projectV2.value.id}`) const interactive = sessionData ? (JSON.parse(sessionData) as ModerationModpackItem[]) : [] const permanentNoData = sessionStorage.getItem( - `modpack-permissions-permanent-no-${props.project.id}`, + `modpack-permissions-permanent-no-${projectV2.value.id}`, ) const permanentNo = permanentNoData ? (JSON.parse(permanentNoData) as ModerationModpackItem[]) @@ -894,7 +906,8 @@ async function assembleFullMessage() { .map((part) => part.content) .filter((content) => content.trim().length > 0) .join('\n\n'), - props.project, + projectV2.value, + projectV3.value, ) return finalMessage @@ -1048,7 +1061,7 @@ function shouldShowStage(stage: Stage): boolean { } if (typeof stage.shouldShow === 'function') { - return stage.shouldShow(props.project) + return stage.shouldShow(projectV2.value, projectV3.value) } return true @@ -1056,7 +1069,7 @@ function shouldShowStage(stage: Stage): boolean { function shouldShowAction(action: Action): boolean { if (typeof action.shouldShow === 'function') { - return action.shouldShow(props.project) + return action.shouldShow(projectV2.value) } return true @@ -1065,7 +1078,7 @@ function shouldShowAction(action: Action): boolean { function getVisibleDropdownOptions(action: DropdownAction) { return action.options.filter((option) => { if (typeof option.shouldShow === 'function') { - return option.shouldShow(props.project) + return option.shouldShow(projectV2.value) } return true }) @@ -1074,7 +1087,7 @@ function getVisibleDropdownOptions(action: DropdownAction) { function getVisibleMultiSelectOptions(action: MultiSelectChipsAction) { return action.options.filter((option) => { if (typeof option.shouldShow === 'function') { - return option.shouldShow(props.project) + return option.shouldShow(projectV2.value) } return true }) @@ -1141,13 +1154,13 @@ async function generateMessage() { loadingMessage.value = true - router.push(`/${props.project.project_type}/${props.project.slug}/moderation`) + router.push(`/${projectV2.value.project_type}/${projectV2.value.slug}/moderation`) try { const baseMessage = await assembleFullMessage() let fullMessage = baseMessage - if (props.project.project_type === 'modpack') { + if (projectV2.value.project_type === 'modpack') { const modpackFilesData = getModpackFilesFromStorage() if (modpackFilesData.interactive.length > 0 || modpackFilesData.permanentNo.length > 0) { @@ -1239,7 +1252,7 @@ function generateModpackMessage(allFiles: { const hasNextProject = ref(false) async function sendMessage(status: ProjectStatus) { try { - await useBaseFetch(`project/${props.project.id}`, { + await useBaseFetch(`project/${projectV2.value.id}`, { method: 'PATCH', body: { status, @@ -1247,7 +1260,7 @@ async function sendMessage(status: ProjectStatus) { }) if (message.value) { - await useBaseFetch(`thread/${props.project.thread_id}`, { + await useBaseFetch(`thread/${projectV2.value.thread_id}`, { method: 'POST', body: { body: { @@ -1259,7 +1272,7 @@ async function sendMessage(status: ProjectStatus) { } if ( - props.project.project_type === 'modpack' && + projectV2.value.project_type === 'modpack' && Object.keys(modpackJudgements.value).length > 0 ) { await useBaseFetch(`moderation/project`, { @@ -1272,7 +1285,7 @@ async function sendMessage(status: ProjectStatus) { done.value = true hasNextProject.value = await moderationStore.completeCurrentProject( - props.project.id, + projectV2.value.id, 'completed', ) } catch (error) { @@ -1326,21 +1339,21 @@ async function endChecklist(status?: string) { } async function skipCurrentProject() { - hasNextProject.value = await moderationStore.completeCurrentProject(props.project.id, 'skipped') + hasNextProject.value = await moderationStore.completeCurrentProject(projectV2.value.id, 'skipped') await endChecklist('skipped') } function clearProjectLocalStorage() { - localStorage.removeItem(`modpack-permissions-${props.project.id}`) - localStorage.removeItem(`modpack-permissions-index-${props.project.id}`) - localStorage.removeItem(`moderation-actions-${props.project.slug}`) - localStorage.removeItem(`moderation-inputs-${props.project.slug}`) - localStorage.removeItem(`moderation-stage-${props.project.slug}`) + localStorage.removeItem(`modpack-permissions-${projectV2.value.id}`) + localStorage.removeItem(`modpack-permissions-index-${projectV2.value.id}`) + localStorage.removeItem(`moderation-actions-${projectV2.value.slug}`) + localStorage.removeItem(`moderation-inputs-${projectV2.value.slug}`) + localStorage.removeItem(`moderation-stage-${projectV2.value.slug}`) - sessionStorage.removeItem(`modpack-permissions-data-${props.project.id}`) - sessionStorage.removeItem(`modpack-permissions-permanent-no-${props.project.id}`) - sessionStorage.removeItem(`modpack-permissions-updated-${props.project.id}`) + sessionStorage.removeItem(`modpack-permissions-data-${projectV2.value.id}`) + sessionStorage.removeItem(`modpack-permissions-permanent-no-${projectV2.value.id}`) + sessionStorage.removeItem(`modpack-permissions-updated-${projectV2.value.id}`) actionStates.value = {} } diff --git a/apps/frontend/src/pages/[type]/[id].vue b/apps/frontend/src/pages/[type]/[id].vue index ec3950890..ebda1791f 100644 --- a/apps/frontend/src/pages/[type]/[id].vue +++ b/apps/frontend/src/pages/[type]/[id].vue @@ -930,7 +930,6 @@ class="moderation-checklist" > project.project_type === 'modpack', + shouldShow: (project: Labrinth.Projects.v2.Project) => project.project_type === 'modpack', actions: [ { id: 'button', diff --git a/packages/moderation/src/data/nags/tags.ts b/packages/moderation/src/data/nags/tags.ts index 9931da62c..a2ad0196c 100644 --- a/packages/moderation/src/data/nags/tags.ts +++ b/packages/moderation/src/data/nags/tags.ts @@ -1,4 +1,4 @@ -import type { Project } from '@modrinth/utils' +import type { Labrinth } from '@modrinth/api-client' import { defineMessage, useVIntl } from '@vintl/vintl' import type { Nag, NagContext } from '../../types/nags' @@ -8,7 +8,7 @@ const allResolutionTags = ['8x-', '16x', '32x', '48x', '64x', '128x', '256x', '5 const MAX_TAG_COUNT = 8 function getCategories( - project: Project & { actualProjectType: string }, + project: Labrinth.Projects.v2.Project & { actualProjectType: string }, tags: { categories?: { project_type: string @@ -120,7 +120,7 @@ export const tagsNags: Nag[] = [ description: (context: NagContext) => { const { formatMessage } = useVIntl() const categoriesForProjectType = getCategories( - context.project as Project & { actualProjectType: string }, + context.project as Labrinth.Projects.v2.Project & { actualProjectType: string }, context.tags, ) const totalAvailableTags = categoriesForProjectType.length @@ -139,7 +139,7 @@ export const tagsNags: Nag[] = [ status: 'required', shouldShow: (context: NagContext) => { const categoriesForProjectType = getCategories( - context.project as Project & { actualProjectType: string }, + context.project as Labrinth.Projects.v2.Project & { actualProjectType: string }, context.tags, ) const totalSelectedTags = diff --git a/packages/moderation/src/data/stages/environment/environment-multiple.ts b/packages/moderation/src/data/stages/environment/environment-multiple.ts new file mode 100644 index 000000000..357ed9e85 --- /dev/null +++ b/packages/moderation/src/data/stages/environment/environment-multiple.ts @@ -0,0 +1,29 @@ +import { GlobeIcon } from '@modrinth/assets' + +import type { ButtonAction } from '../../../types/actions' +import type { Stage } from '../../../types/stage' + +const environmentMultiple: Stage = { + title: "Is the project's environment information accurate?", + id: 'environment', + navigate: '/settings/versions', + icon: GlobeIcon, + guidance_url: 'https://modrinth.com/legal/rules#miscellaneous', + text: async () => + (await import('../../messages/checklist-text/environment/environment-multiple.md?raw')).default, + shouldShow: (project, projectV3) => (projectV3?.environment?.length ?? 0) !== 1, + actions: [ + { + id: 'side_types_inaccurate', + type: 'button', + label: 'Inaccurate', + weight: 800, + suggestedStatus: 'flagged', + severity: 'low', + shouldShow: (project) => project.project_type === 'mod' || project.project_type === 'modpack', + message: async () => (await import('../../messages/environment/inaccurate.md?raw')).default, + } as ButtonAction, + ], +} + +export default environmentMultiple diff --git a/packages/moderation/src/data/stages/environment/environment.ts b/packages/moderation/src/data/stages/environment/environment.ts new file mode 100644 index 000000000..d2a51e978 --- /dev/null +++ b/packages/moderation/src/data/stages/environment/environment.ts @@ -0,0 +1,29 @@ +import { GlobeIcon } from '@modrinth/assets' + +import type { ButtonAction } from '../../../types/actions' +import type { Stage } from '../../../types/stage' + +const environment: Stage = { + title: "Is the project's environment information accurate?", + id: 'environment', + navigate: '/settings/environment', + icon: GlobeIcon, + guidance_url: 'https://modrinth.com/legal/rules#miscellaneous', + text: async () => + (await import('../../messages/checklist-text/environment/environment.md?raw')).default, + shouldShow: (project, projectV3) => (projectV3?.environment?.length ?? 0) === 1, + actions: [ + { + id: 'side_types_inaccurate', + type: 'button', + label: 'Inaccurate', + weight: 800, + suggestedStatus: 'flagged', + severity: 'low', + shouldShow: (project) => project.project_type === 'mod' || project.project_type === 'modpack', + message: async () => (await import('../../messages/environment/inaccurate.md?raw')).default, + } as ButtonAction, + ], +} + +export default environment diff --git a/packages/moderation/src/data/stages/side-types.ts b/packages/moderation/src/data/stages/side-types.ts deleted file mode 100644 index 8d43b8042..000000000 --- a/packages/moderation/src/data/stages/side-types.ts +++ /dev/null @@ -1,38 +0,0 @@ -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/environment', - 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 diff --git a/packages/moderation/src/data/stages/title-slug.ts b/packages/moderation/src/data/stages/title-slug.ts index a90b2eeb6..b2a4215d8 100644 --- a/packages/moderation/src/data/stages/title-slug.ts +++ b/packages/moderation/src/data/stages/title-slug.ts @@ -1,9 +1,9 @@ +import type { Labrinth } from '@modrinth/api-client' import { BookOpenIcon } from '@modrinth/assets' -import type { Project } from '@modrinth/utils' import type { Stage } from '../../types/stage' -function hasCustomSlug(project: Project): boolean { +function hasCustomSlug(project: Labrinth.Projects.v2.Project): boolean { return ( project.slug !== project.title diff --git a/packages/moderation/src/types/actions.ts b/packages/moderation/src/types/actions.ts index 337b7f2e6..e6174116b 100644 --- a/packages/moderation/src/types/actions.ts +++ b/packages/moderation/src/types/actions.ts @@ -1,4 +1,4 @@ -import type { Project } from '@modrinth/utils' +import type { Labrinth } from '@modrinth/api-client' import type { WeightedMessage } from './messages' @@ -60,7 +60,7 @@ export interface BaseAction { * * By default, it returns `true`, meaning the action is always shown. */ - shouldShow?: (project: Project) => boolean + shouldShow?: (project: Labrinth.Projects.v2.Project) => boolean } /** @@ -165,7 +165,7 @@ export interface DropdownActionOption extends WeightedMessage { * * By default, it returns `true`, meaning the option is always shown. */ - shouldShow?: (project: Project) => boolean + shouldShow?: (project: Labrinth.Projects.v2.Project) => boolean } export interface DropdownAction extends BaseAction { @@ -198,7 +198,7 @@ export interface MultiSelectChipsOption extends WeightedMessage { * * By default, it returns `true`, meaning the option is always shown. */ - shouldShow?: (project: Project) => boolean + shouldShow?: (project: Labrinth.Projects.v2.Project) => boolean } export interface MultiSelectChipsAction extends BaseAction { diff --git a/packages/moderation/src/types/keybinds.ts b/packages/moderation/src/types/keybinds.ts index 8bd77fb49..a5524b390 100644 --- a/packages/moderation/src/types/keybinds.ts +++ b/packages/moderation/src/types/keybinds.ts @@ -1,4 +1,4 @@ -import type { Project } from '@modrinth/utils' +import type { Labrinth } from '@modrinth/api-client' export interface ModerationActions { tryGoNext: () => void @@ -44,7 +44,7 @@ export interface ModerationState { } export interface ModerationContext { - project: Project + project: Labrinth.Projects.v2.Project state: ModerationState actions: ModerationActions } diff --git a/packages/moderation/src/types/nags.ts b/packages/moderation/src/types/nags.ts index 68f560a12..5fb206297 100644 --- a/packages/moderation/src/types/nags.ts +++ b/packages/moderation/src/types/nags.ts @@ -1,4 +1,5 @@ -import type { Project, User, Version } from '@modrinth/utils' +import type { Labrinth } from '@modrinth/api-client' +import type { User, Version } from '@modrinth/utils' import type { MessageDescriptor } from '@vintl/vintl' import type { FunctionalComponent, SVGAttributes } from 'vue' @@ -20,7 +21,7 @@ export interface NagContext { /** * The project associated with the nag. */ - project: Project + project: Labrinth.Projects.v2.Project /** * The versions associated with the project. */ diff --git a/packages/moderation/src/types/reports.ts b/packages/moderation/src/types/reports.ts index cb02f8c35..249e457f8 100644 --- a/packages/moderation/src/types/reports.ts +++ b/packages/moderation/src/types/reports.ts @@ -1,4 +1,5 @@ -import type { Project, Report, Thread, User, Version } from '@modrinth/utils' +import type { Labrinth } from '@modrinth/api-client' +import type { Report, Thread, User, Version } from '@modrinth/utils' export interface OwnershipTarget { name: string @@ -10,7 +11,7 @@ export interface OwnershipTarget { export interface ExtendedReport extends Report { thread: Thread reporter_user: User - project?: Project + project?: Labrinth.Projects.v2.Project user?: User version?: Version target?: OwnershipTarget diff --git a/packages/moderation/src/types/stage.ts b/packages/moderation/src/types/stage.ts index ad40ec7b8..5e7eb946d 100644 --- a/packages/moderation/src/types/stage.ts +++ b/packages/moderation/src/types/stage.ts @@ -1,4 +1,4 @@ -import type { Project } from '@modrinth/utils' +import type { Labrinth } from '@modrinth/api-client' import type { FunctionalComponent, SVGAttributes } from 'vue' import type { Action } from './actions' @@ -15,7 +15,10 @@ export interface Stage { /** * An optional description or additional text for the stage. */ - text?: (project: Project) => Promise + text?: ( + project: Labrinth.Projects.v2.Project, + projectV3?: Labrinth.Projects.v3.Project, + ) => Promise /** * Optional id for the stage, used for identification in the checklist. Will be used in the stage list as well instead of the title. @@ -49,5 +52,8 @@ export interface Stage { * * By default, it returns `true`, meaning the stage is always shown. */ - shouldShow?: (project: Project) => boolean + shouldShow?: ( + project: Labrinth.Projects.v2.Project, + projectV3?: Labrinth.Projects.v3.Project, + ) => boolean } diff --git a/packages/moderation/src/utils.ts b/packages/moderation/src/utils.ts index 5e6efdab4..2d3655e46 100644 --- a/packages/moderation/src/utils.ts +++ b/packages/moderation/src/utils.ts @@ -1,4 +1,4 @@ -import type { Project } from '@modrinth/utils' +import type { Labrinth } from '@modrinth/api-client' import type { Action, @@ -210,11 +210,14 @@ export function getVisibleInputs( export function expandVariables( template: string, - project: Project, + project: Labrinth.Projects.v2.Project, + projectV3: Labrinth.Projects.v3.Project, variables?: Record, ): string { - if (!variables) { - variables = flattenProjectVariables(project) + variables ??= { + ...flattenStaticVariables(), + ...flattenProjectVariables(project), + ...flattenProjectV3Variables(projectV3), } return Object.entries(variables).reduce((result, [key, value]) => { @@ -234,7 +237,30 @@ export function arrayOrNone(arr: string[]): string { return arr.length > 0 ? arr.join(', ') : 'None' } -export function flattenProjectVariables(project: Project): Record { +export function flattenStaticVariables(): Record { + const vars: Record = {} + + vars[`RULES`] = `[Modrinth's Content Rules](https://modrinth.com/legal/rules)` + vars[`TOS`] = `[Terms of Use](https://modrinth.com/legal/terms)` + vars[`COPYRIGHT_POLICY`] = `[Copyright Policy](https://modrinth.com/legal/copyright)` + vars[`SUPPORT`] = + `please visit the [Modrinth Help Center](https://support.modrinth.com/) and click the blue bubble to contact support.` + vars[`MODPACK_PERMISSIONS_GUIDE`] = + `our guide to [Obtaining Modpack Permissions](https://support.modrinth.com/en/articles/8797527-obtaining-modpack-permissions)` + vars[`MODPACKS_ON_MODRINTH`] = + `[Modpacks on Modrinth](https://support.modrinth.com/en/articles/8802250-modpacks-on-modrinth)` + vars[`ADVANCED_MARKDOWN`] = + `[Markdown Formatting Guide](https://support.modrinth.com/en/articles/8801962-advanced-markdown-formatting)` + vars[`LICENSING_GUIDE`] = + `our guide to [Licensing your Mods](https://modrinth.com/news/article/licensing-guide)` + vars[`NEW_ENVIRONMENTS_LINK`] = `https://modrinth.com/news/article/new-environments` + + return vars +} + +export function flattenProjectVariables( + project: Labrinth.Projects.v2.Project, +): Record { const vars: Record = {} vars['PROJECT_ID'] = project.id @@ -299,22 +325,6 @@ export function flattenProjectVariables(project: Project): Record { + const vars: Record = {} + + const environment = projectV3.environment ?? [] + vars['PROJECT_V3_ENVIRONMENT_COUNT'] = environment.length.toString() + vars['PROJECT_V3_ALL_ENVIRONMENTS'] = environment.join(', ') + + environment.forEach((env, index) => { + vars[`PROJECT_V3_ENVIRONMENT_${index}`] = env + }) + + vars['PROJECT_V3_REVIEW_STATUS'] = projectV3.side_types_migration_review_status + vars['PROJECT_V3_TYPES'] = projectV3.project_types.join(', ') + + return vars +} From 336832ec40df7cf65fd7bce3c401833aa3d6bd57 Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Tue, 23 Dec 2025 15:40:42 +0000 Subject: [PATCH 006/158] feat: downtime banner (#4955) --- apps/frontend/src/layouts/default.vue | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue index e5d1fe387..c69c3bbc3 100644 --- a/apps/frontend/src/layouts/default.vue +++ b/apps/frontend/src/layouts/default.vue @@ -34,6 +34,33 @@ 'modrinth-parent__no-modal-blurs': !cosmetics.advancedRendering, }" > + + + + + + Date: Tue, 23 Dec 2025 16:55:47 +0100 Subject: [PATCH 007/158] Revert "feat: downtime banner (#4955)" This reverts commit 336832ec40df7cf65fd7bce3c401833aa3d6bd57. --- apps/frontend/src/layouts/default.vue | 31 +-------------------------- 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue index c69c3bbc3..e5d1fe387 100644 --- a/apps/frontend/src/layouts/default.vue +++ b/apps/frontend/src/layouts/default.vue @@ -34,33 +34,6 @@ 'modrinth-parent__no-modal-blurs': !cosmetics.advancedRendering, }" > - - - - - - Date: Wed, 24 Dec 2025 13:24:05 -0800 Subject: [PATCH 008/158] devex: use tailwind preset for website, app, and ui package (#4964) * use tailwind preset for website, app, and ui package * fix preset import --- apps/app-frontend/tailwind.config.ts | 241 +--------------- apps/frontend/tailwind.config.ts | 257 +----------------- packages/tooling-config/package.json | 4 +- .../tailwind/tailwind-preset.ts} | 16 +- packages/ui/tailwind.config.js | 5 - packages/ui/tailwind.config.ts | 9 + 6 files changed, 21 insertions(+), 511 deletions(-) rename packages/{ui/tailwind-preset.js => tooling-config/tailwind/tailwind-preset.ts} (96%) delete mode 100644 packages/ui/tailwind.config.js create mode 100644 packages/ui/tailwind.config.ts diff --git a/apps/app-frontend/tailwind.config.ts b/apps/app-frontend/tailwind.config.ts index d3323e11e..e81246e21 100644 --- a/apps/app-frontend/tailwind.config.ts +++ b/apps/app-frontend/tailwind.config.ts @@ -1,3 +1,4 @@ +import preset from '@modrinth/tooling-config/tailwind/tailwind-preset.ts' import type { Config } from 'tailwindcss' const config: Config = { @@ -12,245 +13,7 @@ const config: Config = { '../../packages/**/*.{js,vue,ts}', '!../../packages/**/node_modules/**', ], - theme: { - extend: { - colors: { - surface: { - 1: 'var(--surface-1)', - 2: 'var(--surface-2)', - 3: 'var(--surface-3)', - 4: 'var(--surface-4)', - 5: 'var(--surface-5)', - }, - - /// TODO: Clean up these aliases within codebase to use default, primary, tertiary. - // text-default - primary: 'var(--color-text-default)', - - // text-primary - contrast: 'var(--color-text-primary)', - - // text-tertiary - secondary: 'var(--color-text-tertiary)', - - red: { - DEFAULT: 'var(--color-red)', - 50: 'var(--color-red-50)', - 100: 'var(--color-red-100)', - 200: 'var(--color-red-200)', - 300: 'var(--color-red-300)', - 400: 'var(--color-red-400)', - 500: 'var(--color-red-500)', - 600: 'var(--color-red-600)', - 700: 'var(--color-red-700)', - 800: 'var(--color-red-800)', - 900: 'var(--color-red-900)', - 950: 'var(--color-red-950)', - }, - orange: { - DEFAULT: 'var(--color-orange)', - 50: 'var(--color-orange-50)', - 100: 'var(--color-orange-100)', - 200: 'var(--color-orange-200)', - 300: 'var(--color-orange-300)', - 400: 'var(--color-orange-400)', - 500: 'var(--color-orange-500)', - 600: 'var(--color-orange-600)', - 700: 'var(--color-orange-700)', - 800: 'var(--color-orange-800)', - 900: 'var(--color-orange-900)', - 950: 'var(--color-orange-950)', - }, - green: { - DEFAULT: 'var(--color-green)', - 50: 'var(--color-green-50)', - 100: 'var(--color-green-100)', - 200: 'var(--color-green-200)', - 300: 'var(--color-green-300)', - 400: 'var(--color-green-400)', - 500: 'var(--color-green-500)', - 600: 'var(--color-green-600)', - 700: 'var(--color-green-700)', - 800: 'var(--color-green-800)', - 900: 'var(--color-green-900)', - 950: 'var(--color-green-950)', - }, - blue: { - DEFAULT: 'var(--color-blue)', - 50: 'var(--color-blue-50)', - 100: 'var(--color-blue-100)', - 200: 'var(--color-blue-200)', - 300: 'var(--color-blue-300)', - 400: 'var(--color-blue-400)', - 500: 'var(--color-blue-500)', - 600: 'var(--color-blue-600)', - 700: 'var(--color-blue-700)', - 800: 'var(--color-blue-800)', - 900: 'var(--color-blue-900)', - 950: 'var(--color-blue-950)', - }, - purple: { - DEFAULT: 'var(--color-purple)', - 50: 'var(--color-purple-50)', - 100: 'var(--color-purple-100)', - 200: 'var(--color-purple-200)', - 300: 'var(--color-purple-300)', - 400: 'var(--color-purple-400)', - 500: 'var(--color-purple-500)', - 600: 'var(--color-purple-600)', - 700: 'var(--color-purple-700)', - 800: 'var(--color-purple-800)', - 900: 'var(--color-purple-900)', - 950: 'var(--color-purple-950)', - }, - gray: { - DEFAULT: 'var(--color-gray)', - 50: 'var(--color-gray-50)', - 100: 'var(--color-gray-100)', - 200: 'var(--color-gray-200)', - 300: 'var(--color-gray-300)', - 400: 'var(--color-gray-400)', - 500: 'var(--color-gray-500)', - 600: 'var(--color-gray-600)', - 700: 'var(--color-gray-700)', - 800: 'var(--color-gray-800)', - 900: 'var(--color-gray-900)', - 950: 'var(--color-gray-950)', - }, - - /// === LEGACY === - icon: 'var(--color-base)', - // Text - inactive: 'var(--color-text-inactive)', - dark: 'var(--color-text-dark)', - inverted: 'var(--color-text-inverted)', - heading: 'var(--color-heading)', - bg: { - DEFAULT: 'var(--color-bg)', - red: 'var(--color-red-bg)', - orange: 'var(--color-orange-bg)', - green: 'var(--color-green-bg)', - blue: 'var(--color-blue-bg)', - purple: 'var(--color-purple-bg)', - raised: 'var(--color-raised-bg)', - }, - highlight: { - DEFAULT: 'var(--color-brand-highlight)', - red: 'var(--color-red-highlight)', - orange: 'var(--color-orange-highlight)', - green: 'var(--color-green-highlight)', - blue: 'var(--color-blue-highlight)', - purple: 'var(--color-purple-highlight)', - gray: 'var(--color-gray-highlight)', - }, - divider: { - DEFAULT: 'var(--color-divider)', - dark: 'var(--color-divider-dark)', - }, - brand: { - DEFAULT: 'var(--color-brand)', - red: 'var(--color-red)', - orange: 'var(--color-orange)', - green: 'var(--color-green)', - blue: 'var(--color-blue)', - purple: 'var(--color-purple)', - highlight: 'var(--color-brand-highlight)', - shadow: 'var(--color-brand-shadow)', - inverted: 'var(--color-accent-contrast)', - }, - tabUnderlineHovered: 'var(--tab-underline-hovered)', - button: { - bg: 'var(--color-button-bg)', - text: 'var(--color-button-text)', - bgHover: 'var(--color-button-bg-hover)', - textHover: 'var(--color-button-text-hover)', - bgActive: 'var(--color-button-bg-active)', - textActive: 'var(--color-button-text-active)', - border: 'var(--color-button-border)', - bgSelected: 'var(--color-button-bg-selected)', - textSelected: 'var(--color-button-text-selected)', - }, - toggleHandle: 'var(--color-toggle-handle)', - dropdown: { - bg: 'var(--color-dropdown-bg)', - text: 'var(--color-dropdown-text)', - }, - tooltip: { - bg: 'var(--color-tooltip-bg)', - text: 'var(--color-tooltip-text)', - }, - code: { - bg: 'var(--color-code-bg)', - text: 'var(--color-code-text)', - }, - kbdShadow: 'var(--color-kbd-shadow)', - ad: { - DEFAULT: 'var(--color-ad)', - raised: 'var(--color-ad-raised)', - contrast: 'var(--color-ad-contrast)', - highlight: 'var(--color-ad-highlight)', - }, - greyLink: { - DEFAULT: 'var(--color-grey-link)', - hover: 'var(--color-grey-link-hover)', - active: 'var(--color-grey-link-active)', - }, - link: { - DEFAULT: 'var(--color-link)', - hover: 'var(--color-link-hover)', - active: 'var(--color-link-active)', - }, - warning: { - bg: 'var(--color-warning-bg)', - text: 'var(--color-warning-text)', - banner: { - text: 'var(--color-warning-banner-text)', - bg: 'var(--color-warning-banner-bg)', - side: 'var(--color-warning-banner-side)', - }, - }, - infoBanner: { - text: 'var(--color-info-banner-text)', - bg: 'var(--color-info-banner-bg)', - side: 'var(--color-info-banner-side)', - }, - blockQuote: 'var(--color-block-quote)', - headerUnderline: 'var(--color-header-underline)', - hr: 'var(--color-hr)', - table: { - border: 'var(--color-table-border)', - alternateRow: ' var(--color-table-alternate-row)', - }, - }, - backgroundImage: { - mazeBg: 'var(--landing-maze-bg)', - mazeGradientBg: 'var(--landing-maze-gradient-bg)', - // @ts-ignore - landing: { - mazeOuterBg: 'var(--landing-maze-outer-bg)', - colorHeading: 'var(--landing-color-heading)', - colorSubheading: 'var(--landing-color-subheading)', - transitionGradientStart: 'var(--landing-transition-gradient-start)', - transitionGradientEnd: 'var(--landing-transition-gradient-end)', - hoverCardGradient: 'var(--landing-hover-card-gradient)', - borderGradient: 'var(--landing-border-gradient)', - borderColor: 'var(--landing-border-color)', - creatorGradient: 'var(--landing-creator-gradient)', - blobGradient: 'var(--landing-blob-gradient)', - cardBg: 'var(--landing-card-bg)', - blueLabel: 'var(--landing-blue-label)', - blueLabelBg: 'var(--landing-blue-label-bg)', - greenLabel: 'var(--landing-green-label)', - greenLabelBg: 'var(--landing-green-label-bg)', - rawBg: 'var(--landing-raw-bg)', - }, - }, - }, - }, - plugins: [], - corePlugins: { - preflight: false, - }, + presets: [preset], } export default config diff --git a/apps/frontend/tailwind.config.ts b/apps/frontend/tailwind.config.ts index 69bf2f163..dd57bbcd2 100644 --- a/apps/frontend/tailwind.config.ts +++ b/apps/frontend/tailwind.config.ts @@ -1,3 +1,4 @@ +import preset from '@modrinth/tooling-config/tailwind/tailwind-preset.ts' import type { Config } from 'tailwindcss' const config: Config = { @@ -12,261 +13,7 @@ const config: Config = { '../../packages/**/*.{js,vue,ts}', '!../../packages/**/node_modules/**', ], - theme: { - extend: { - colors: { - surface: { - 1: 'var(--surface-1)', - 2: 'var(--surface-2)', - 3: 'var(--surface-3)', - 4: 'var(--surface-4)', - 5: 'var(--surface-5)', - }, - - /// TODO: Clean up these aliases within codebase to use default, primary, tertiary. - // text-default - primary: 'var(--color-text-default)', - - // text-primary - contrast: 'var(--color-text-primary)', - - // text-tertiary - secondary: 'var(--color-text-tertiary)', - - red: { - DEFAULT: 'var(--color-red)', - 50: 'var(--color-red-50)', - 100: 'var(--color-red-100)', - 200: 'var(--color-red-200)', - 300: 'var(--color-red-300)', - 400: 'var(--color-red-400)', - 500: 'var(--color-red-500)', - 600: 'var(--color-red-600)', - 700: 'var(--color-red-700)', - 800: 'var(--color-red-800)', - 900: 'var(--color-red-900)', - 950: 'var(--color-red-950)', - }, - orange: { - DEFAULT: 'var(--color-orange)', - 50: 'var(--color-orange-50)', - 100: 'var(--color-orange-100)', - 200: 'var(--color-orange-200)', - 300: 'var(--color-orange-300)', - 400: 'var(--color-orange-400)', - 500: 'var(--color-orange-500)', - 600: 'var(--color-orange-600)', - 700: 'var(--color-orange-700)', - 800: 'var(--color-orange-800)', - 900: 'var(--color-orange-900)', - 950: 'var(--color-orange-950)', - }, - green: { - DEFAULT: 'var(--color-green)', - 50: 'var(--color-green-50)', - 100: 'var(--color-green-100)', - 200: 'var(--color-green-200)', - 300: 'var(--color-green-300)', - 400: 'var(--color-green-400)', - 500: 'var(--color-green-500)', - 600: 'var(--color-green-600)', - 700: 'var(--color-green-700)', - 800: 'var(--color-green-800)', - 900: 'var(--color-green-900)', - 950: 'var(--color-green-950)', - }, - blue: { - DEFAULT: 'var(--color-blue)', - 50: 'var(--color-blue-50)', - 100: 'var(--color-blue-100)', - 200: 'var(--color-blue-200)', - 300: 'var(--color-blue-300)', - 400: 'var(--color-blue-400)', - 500: 'var(--color-blue-500)', - 600: 'var(--color-blue-600)', - 700: 'var(--color-blue-700)', - 800: 'var(--color-blue-800)', - 900: 'var(--color-blue-900)', - 950: 'var(--color-blue-950)', - }, - purple: { - DEFAULT: 'var(--color-purple)', - 50: 'var(--color-purple-50)', - 100: 'var(--color-purple-100)', - 200: 'var(--color-purple-200)', - 300: 'var(--color-purple-300)', - 400: 'var(--color-purple-400)', - 500: 'var(--color-purple-500)', - 600: 'var(--color-purple-600)', - 700: 'var(--color-purple-700)', - 800: 'var(--color-purple-800)', - 900: 'var(--color-purple-900)', - 950: 'var(--color-purple-950)', - }, - gray: { - DEFAULT: 'var(--color-gray)', - 50: 'var(--color-gray-50)', - 100: 'var(--color-gray-100)', - 200: 'var(--color-gray-200)', - 300: 'var(--color-gray-300)', - 400: 'var(--color-gray-400)', - 500: 'var(--color-gray-500)', - 600: 'var(--color-gray-600)', - 700: 'var(--color-gray-700)', - 800: 'var(--color-gray-800)', - 900: 'var(--color-gray-900)', - 950: 'var(--color-gray-950)', - }, - - /// === LEGACY === - icon: 'var(--color-base)', - // Text - inactive: 'var(--color-text-inactive)', - dark: 'var(--color-text-dark)', - inverted: 'var(--color-text-inverted)', - heading: 'var(--color-heading)', - bg: { - DEFAULT: 'var(--surface-1)', // var(--color-bg) - red: 'var(--color-red-bg)', - orange: 'var(--color-orange-bg)', - green: 'var(--color-green-bg)', - blue: 'var(--color-blue-bg)', - purple: 'var(--color-purple-bg)', - raised: 'var(--surface-3)', // var(--color-raised-bg) - }, - banners: { - error: { - bg: 'var(--banner-error-bg)', - text: 'var(--banner-error-text)', - border: 'var(--banner-error-border)', - }, - warning: { - bg: 'var(--banner-warning-bg)', - text: 'var(--banner-warning-text)', - border: 'var(--banner-warning-border)', - }, - info: { - bg: 'var(--banner-info-bg)', - text: 'var(--banner-info-text)', - border: 'var(--banner-info-border)', - }, - }, - highlight: { - DEFAULT: 'var(--color-brand-highlight)', - red: 'var(--color-red-highlight)', - orange: 'var(--color-orange-highlight)', - green: 'var(--color-green-highlight)', - blue: 'var(--color-blue-highlight)', - purple: 'var(--color-purple-highlight)', - }, - divider: { - DEFAULT: 'var(--color-divider)', - dark: 'var(--color-divider-dark)', - }, - brand: { - DEFAULT: 'var(--color-brand)', - red: 'var(--color-red)', - orange: 'var(--color-orange)', - green: 'var(--color-green)', - blue: 'var(--color-blue)', - purple: 'var(--color-purple)', - highlight: 'var(--color-brand-highlight)', - shadow: 'var(--color-brand-shadow)', - inverted: 'var(--color-accent-contrast)', - }, - tabUnderlineHovered: 'var(--tab-underline-hovered)', - button: { - bg: 'var(--color-button-bg)', - text: 'var(--color-button-text)', - bgHover: 'var(--color-button-bg-hover)', - textHover: 'var(--color-button-text-hover)', - bgActive: 'var(--color-button-bg-active)', - textActive: 'var(--color-button-text-active)', - border: 'var(--color-button-border)', - bgSelected: 'var(--color-button-bg-selected)', - textSelected: 'var(--color-button-text-selected)', - }, - toggleHandle: 'var(--color-toggle-handle)', - dropdown: { - bg: 'var(--color-dropdown-bg)', - text: 'var(--color-dropdown-text)', - }, - tooltip: { - bg: 'var(--color-tooltip-bg)', - text: 'var(--color-tooltip-text)', - }, - code: { - bg: 'var(--color-code-bg)', - text: 'var(--color-code-text)', - }, - kbdShadow: 'var(--color-kbd-shadow)', - ad: { - DEFAULT: 'var(--color-ad)', - raised: 'var(--color-ad-raised)', - contrast: 'var(--color-ad-contrast)', - highlight: 'var(--color-ad-highlight)', - }, - greyLink: { - DEFAULT: 'var(--color-grey-link)', - hover: 'var(--color-grey-link-hover)', - active: 'var(--color-grey-link-active)', - }, - link: { - DEFAULT: 'var(--color-link)', - hover: 'var(--color-link-hover)', - active: 'var(--color-link-active)', - }, - warning: { - bg: 'var(--color-warning-bg)', - text: 'var(--color-warning-text)', - banner: { - text: 'var(--color-warning-banner-text)', - bg: 'var(--color-warning-banner-bg)', - side: 'var(--color-warning-banner-side)', - }, - }, - infoBanner: { - text: 'var(--color-info-banner-text)', - bg: 'var(--color-info-banner-bg)', - side: 'var(--color-info-banner-side)', - }, - blockQuote: 'var(--color-block-quote)', - headerUnderline: 'var(--color-header-underline)', - hr: 'var(--color-hr)', - table: { - border: 'var(--color-table-border)', - alternateRow: ' var(--color-table-alternate-row)', - }, - }, - backgroundImage: { - mazeBg: 'var(--landing-maze-bg)', - mazeGradientBg: 'var(--landing-maze-gradient-bg)', - // @ts-ignore - landing: { - mazeOuterBg: 'var(--landing-maze-outer-bg)', - colorHeading: 'var(--landing-color-heading)', - colorSubheading: 'var(--landing-color-subheading)', - transitionGradientStart: 'var(--landing-transition-gradient-start)', - transitionGradientEnd: 'var(--landing-transition-gradient-end)', - hoverCardGradient: 'var(--landing-hover-card-gradient)', - borderGradient: 'var(--landing-border-gradient)', - borderColor: 'var(--landing-border-color)', - creatorGradient: 'var(--landing-creator-gradient)', - blobGradient: 'var(--landing-blob-gradient)', - cardBg: 'var(--landing-card-bg)', - blueLabel: 'var(--landing-blue-label)', - blueLabelBg: 'var(--landing-blue-label-bg)', - greenLabel: 'var(--landing-green-label)', - greenLabelBg: 'var(--landing-green-label-bg)', - rawBg: 'var(--landing-raw-bg)', - }, - }, - }, - }, - plugins: [], - corePlugins: { - preflight: false, - }, + presets: [preset], } export default config diff --git a/packages/tooling-config/package.json b/packages/tooling-config/package.json index e4e7c66f8..0839c1578 100644 --- a/packages/tooling-config/package.json +++ b/packages/tooling-config/package.json @@ -14,12 +14,14 @@ "./prettier.config.cjs": "./prettier.config.cjs", "./frontend.prettier.config.cjs": "./frontend.prettier.config.cjs", "./app-lib.prettier.config.cjs": "./app-lib.prettier.config.cjs", - "./labrinth.prettier.config.cjs": "./labrinth.prettier.config.cjs" + "./labrinth.prettier.config.cjs": "./labrinth.prettier.config.cjs", + "./tailwind/*": "./tailwind/*" }, "files": [ "eslint/", "typescript/", "prettier/", + "tailwind/", "index.js" ], "peerDependencies": { diff --git a/packages/ui/tailwind-preset.js b/packages/tooling-config/tailwind/tailwind-preset.ts similarity index 96% rename from packages/ui/tailwind-preset.js rename to packages/tooling-config/tailwind/tailwind-preset.ts index f73ed47d3..3c6ca2a69 100644 --- a/packages/ui/tailwind-preset.js +++ b/packages/tooling-config/tailwind/tailwind-preset.ts @@ -1,14 +1,6 @@ -module.exports = { - content: [ - './src/components/**/*.{js,vue,ts}', - './src/layouts/**/*.vue', - './src/pages/**/*.vue', - './src/plugins/**/*.{js,ts}', - './src/app.vue', - './src/error.vue', - // monorepo - TODO: migrate this to its own package - '../../packages/**/*.{js,vue,ts}', - ], +import type { Config } from 'tailwindcss' + +const config: Config = { theme: { extend: { colors: { @@ -265,3 +257,5 @@ module.exports = { preflight: false, }, } + +export default config diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js deleted file mode 100644 index 1160b1825..000000000 --- a/packages/ui/tailwind.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ['./tailwind-preset.js'], - presets: [require('./tailwind-preset.js')], -} diff --git a/packages/ui/tailwind.config.ts b/packages/ui/tailwind.config.ts new file mode 100644 index 000000000..5ee87de44 --- /dev/null +++ b/packages/ui/tailwind.config.ts @@ -0,0 +1,9 @@ +import preset from '@modrinth/tooling-config/tailwind/tailwind-preset.ts' +import type { Config } from 'tailwindcss' + +const config: Config = { + content: ['./src/components/**/*.{js,vue,ts}', './src/pages/**/*.{js,vue,ts}'], + presets: [preset], +} + +export default config From 3adee66899ba3f203d22b0b87a694a1626cb01e1 Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Wed, 24 Dec 2025 21:39:59 +0000 Subject: [PATCH 009/158] devex: prepr:web and app with proper caching (#4957) * devex: prepr:web and app with proper caching * fix: add tooling config to turbo global deps * fix: exclude turbo + node modules for tooling-config * feat: prepr:frontend * fix: ci issue --- .github/workflows/turbo-ci.yml | 2 +- CLAUDE.md | 4 ++-- package.json | 17 ++++++----------- turbo.jsonc | 13 ++++++++++--- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/turbo-ci.yml b/.github/workflows/turbo-ci.yml index ba62ac5a5..b0d4e4ad3 100644 --- a/.github/workflows/turbo-ci.yml +++ b/.github/workflows/turbo-ci.yml @@ -88,5 +88,5 @@ jobs: - name: 🔍 Verify intl:extract has been run run: | - pnpm intl:extract + pnpm turbo run intl:extract --force git diff --exit-code --color */*/src/locales/en-US/index.json diff --git a/CLAUDE.md b/CLAUDE.md index 0ab19bc52..42161ddf3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,13 +16,13 @@ Both have access to our dependency injection framework, examples as seen in `pac ### Website (apps/frontend) -Before a pull request can be opened for the website, `pnpm web:fix` and `pnpm web:intl:extract` must be run, otherwise CI will fail. +Before a pull request can be opened for the website, run `pnpm prepr:frontend:web` from the root folder, otherwise CI will fail. To run a development version of the frontend, you must first copy over the relevant `.env` template file (prod, staging or local, usually prod) within the `apps/frontend` folder into `apps/frontend/.env`. Then you can run the frontend by running `pnpm web:dev` in the root folder. ### App Frontend (apps/app-frontend) -Before a pull request can be opened for the website, you must CD into the `app-frontend` folder; `pnpm fix` and `pnpm intl:extract` must be run, otherwise CI will fail. +Before a pull request can be opened for the app frontend, run `pnpm prepr:frontend:app` from the root folder, otherwise CI will fail. To run a development version of the app frontend, you must first copy over the relevant `.env` template file (prod, staging or local, usually prod) within `packages/app-lib` into `packages/app-lib/.env`. Then you must run the app itself by running `pnpm app:dev` in the root folder. diff --git a/package.json b/package.json index f9c7d2343..6ae915412 100644 --- a/package.json +++ b/package.json @@ -3,29 +3,24 @@ "version": "0.0.0", "private": true, "scripts": { - "ui:intl:extract": "pnpm run --filter=@modrinth/ui intl:extract", "web:dev": "turbo run dev --filter=@modrinth/frontend", "web:build": "turbo run build --filter=@modrinth/frontend", - "web:fix": "turbo run fix --filter=@modrinth/frontend", - "web:intl:extract": "pnpm run --filter=@modrinth/frontend intl:extract", "app:dev": "turbo run dev --filter=@modrinth/app", - "docs:dev": "turbo run dev --filter=@modrinth/docs", "app:build": "turbo run build --filter=@modrinth/app", - "app:fix": "turbo run fix --filter=@modrinth/app", - "app:intl:extract": "pnpm run --filter=@modrinth/app-frontend intl:extract", - "blog:fix": "turbo run fix --filter=@modrinth/blog", + "docs:dev": "turbo run dev --filter=@modrinth/docs", "pages:build": "NITRO_PRESET=cloudflare-pages pnpm --filter frontend run build", - "moderation:fix": "turbo run fix --filter=@modrinth/moderation", - "moderation:intl:extract": "pnpm run --filter=@modrinth/moderation intl:extract", "build": "turbo run build --continue", "lint": "turbo run lint lint:ancillary --continue", "lint:ancillary": "prettier --check .github *.*", "test": "turbo run test --continue", "fix": "turbo run fix fix:ancillary --continue", - "fix:frontend": "turbo run fix --continue --parallel --filter='!@modrinth/labrinth' --filter='!@modrinth/app' --filter='!@modrinth/app-lib' --filter='!@modrinth/daedalus' --filter='!@modrinth/daedalus_client' --filter='!@modrinth/app-playground'", "fix:ancillary": "prettier --write .github *.*", "ci": "turbo run lint test --continue", - "intl:extract": "pnpm ui:intl:extract && pnpm web:intl:extract && pnpm app:intl:extract && pnpm moderation:intl:extract" + "prepr": "turbo run prepr --continue", + "prepr:frontend": "turbo run prepr --filter=@modrinth/frontend --filter=@modrinth/app-frontend", + "prepr:frontend:lib": "turbo run prepr --filter=@modrinth/ui --filter=@modrinth/moderation --filter=@modrinth/assets --filter=@modrinth/blog --filter=@modrinth/api-client --filter=@modrinth/utils --filter=@modrinth/tooling-config", + "prepr:frontend:web": "turbo run prepr --filter=@modrinth/frontend", + "prepr:frontend:app": "turbo run prepr --filter=@modrinth/app-frontend" }, "devDependencies": { "@modrinth/tooling-config": "workspace:*", diff --git a/turbo.jsonc b/turbo.jsonc index b9aeb835d..aec2b3438 100644 --- a/turbo.jsonc +++ b/turbo.jsonc @@ -1,6 +1,11 @@ { "$schema": "./node_modules/turbo/schema.json", "concurrency": "100%", + "globalDependencies": [ + "packages/tooling-config/**", + "!packages/tooling-config/.turbo/**", + "!packages/tooling-config/node_modules/**" + ], "tasks": { "build": { "dependsOn": ["^build"], @@ -60,11 +65,13 @@ "NEXTEST_*" ] }, - "fix": { - "cache": false - }, + "fix": {}, "fix:ancillary": { "cache": false + }, + "intl:extract": {}, + "prepr": { + "dependsOn": ["^prepr", "fix", "intl:extract"] } } } From 1f21d66140f23d37d65eb60b016e2adb352e4657 Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Wed, 24 Dec 2025 22:30:46 +0000 Subject: [PATCH 010/158] devex: add icon cmd (#4958) * feat: icons add cmd * fix: dep * Update packages/assets/build/add-icons.ts Signed-off-by: Calum H. * fix: lint --------- Signed-off-by: Calum H. Signed-off-by: Calum H. --- package.json | 3 +- packages/assets/build/add-icons.ts | 213 +++++++++++++++++++++++++++++ packages/assets/package.json | 3 + pnpm-lock.yaml | 11 ++ 4 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 packages/assets/build/add-icons.ts diff --git a/package.json b/package.json index 6ae915412..94b2206a6 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "prepr:frontend": "turbo run prepr --filter=@modrinth/frontend --filter=@modrinth/app-frontend", "prepr:frontend:lib": "turbo run prepr --filter=@modrinth/ui --filter=@modrinth/moderation --filter=@modrinth/assets --filter=@modrinth/blog --filter=@modrinth/api-client --filter=@modrinth/utils --filter=@modrinth/tooling-config", "prepr:frontend:web": "turbo run prepr --filter=@modrinth/frontend", - "prepr:frontend:app": "turbo run prepr --filter=@modrinth/app-frontend" + "prepr:frontend:app": "turbo run prepr --filter=@modrinth/app-frontend", + "icons:add": "pnpm --filter @modrinth/assets icons:add" }, "devDependencies": { "@modrinth/tooling-config": "workspace:*", diff --git a/packages/assets/build/add-icons.ts b/packages/assets/build/add-icons.ts new file mode 100644 index 000000000..ed5480557 --- /dev/null +++ b/packages/assets/build/add-icons.ts @@ -0,0 +1,213 @@ +import fs from 'node:fs' +import path from 'node:path' +import readline from 'node:readline' + +const packageRoot = path.resolve(__dirname, '..') +const iconsDir = path.join(packageRoot, 'icons') +const lucideIconsDir = path.join(packageRoot, 'node_modules/lucide-static/icons') + +function listAvailableIcons(): string[] { + if (!fs.existsSync(lucideIconsDir)) { + return [] + } + return fs + .readdirSync(lucideIconsDir) + .filter((file) => file.endsWith('.svg')) + .map((file) => path.basename(file, '.svg')) + .sort() +} + +function paginateList(allIcons: string[], pageSize = 20): void { + let page = 0 + let search = '' + let filteredIcons = allIcons + + const getFilteredIcons = (): string[] => { + if (!search) return allIcons + return allIcons.filter((icon) => icon.includes(search)) + } + + const renderPage = (): void => { + console.clear() + filteredIcons = getFilteredIcons() + const totalPages = Math.max(1, Math.ceil(filteredIcons.length / pageSize)) + + if (page >= totalPages) page = Math.max(0, totalPages - 1) + + const start = page * pageSize + const end = Math.min(start + pageSize, filteredIcons.length) + const pageIcons = filteredIcons.slice(start, end) + + console.log(`\x1b[1mAvailable Lucide Icons\x1b[0m`) + console.log(`\x1b[2mSearch: \x1b[0m${search || '\x1b[2m(type to search)\x1b[0m'}\n`) + + if (pageIcons.length === 0) { + console.log(` \x1b[2mNo icons found matching "${search}"\x1b[0m`) + } else { + pageIcons.forEach((icon) => { + if (search) { + const highlighted = icon.replace(search, `\x1b[33m${search}\x1b[0m`) + console.log(` ${highlighted}`) + } else { + console.log(` ${icon}`) + } + }) + } + + console.log( + `\n\x1b[2m${filteredIcons.length}/${allIcons.length} icons | Page ${page + 1}/${totalPages} | ← → navigate | :q quit\x1b[0m`, + ) + } + + renderPage() + + readline.emitKeypressEvents(process.stdin) + if (process.stdin.isTTY) { + process.stdin.setRawMode(true) + } + + process.stdin.on('keypress', (str, key) => { + if (key.ctrl && key.name === 'c') { + console.clear() + process.exit(0) + } + + // :q to quit + if (search === ':' && key.name === 'q') { + console.clear() + process.exit(0) + } + + // Navigation + if (key.name === 'right') { + const totalPages = Math.max(1, Math.ceil(filteredIcons.length / pageSize)) + if (page < totalPages - 1) { + page++ + renderPage() + } + return + } + if (key.name === 'left') { + if (page > 0) { + page-- + renderPage() + } + return + } + + // Backspace + if (key.name === 'backspace') { + search = search.slice(0, -1) + page = 0 + renderPage() + return + } + + // Escape to clear search + if (key.name === 'escape') { + search = '' + page = 0 + renderPage() + return + } + + // Type to search + if (str && str.length === 1 && !key.ctrl && !key.meta) { + search += str + page = 0 + renderPage() + } + }) +} + +function addIcon(iconId: string, overwrite: boolean): boolean { + const sourcePath = path.join(lucideIconsDir, `${iconId}.svg`) + const targetPath = path.join(iconsDir, `${iconId}.svg`) + + if (!fs.existsSync(sourcePath)) { + console.error(`❌ Icon "${iconId}" not found in lucide-static`) + console.error(` Run with --list to see available icons`) + return false + } + + if (fs.existsSync(targetPath) && !overwrite) { + console.log(`⏭️ Skipping "${iconId}" (already exists, use --overwrite to replace)`) + return false + } + + fs.copyFileSync(sourcePath, targetPath) + console.log(`✅ Added "${iconId}"`) + return true +} + +function main(): void { + const args = process.argv.slice(2) + + if (args.includes('--help') || args.includes('-h')) { + console.log(` +Usage: pnpm icons:add [options] [icon_id...] + +Options: + --list, -l Browse all available Lucide icons (interactive) + --overwrite, -o Overwrite existing icons + --help, -h Show this help message + +Examples: + pnpm icons:add heart star settings-2 + pnpm icons:add --overwrite heart + pnpm icons:add --list # Interactive browser + pnpm icons:add --list | grep arrow # Pipe to grep + +Interactive controls: + Type Search icons + ← → Navigate pages + Escape Clear search + :q Quit +`) + process.exit(0) + } + + if (args.includes('--list') || args.includes('-l')) { + const icons = listAvailableIcons() + if (icons.length === 0) { + console.error('❌ lucide-static not installed. Run pnpm install first.') + process.exit(1) + } + if (process.stdout.isTTY) { + paginateList(icons) + } else { + // Non-interactive mode (piped output) + icons.forEach((icon) => console.log(icon)) + process.exit(0) + } + return + } + + const overwrite = args.includes('--overwrite') || args.includes('-o') + const iconIds = args.filter((arg) => !arg.startsWith('-')) + + if (iconIds.length === 0) { + console.error('Usage: pnpm icons:add [icon_id...]') + console.error('Example: pnpm icons:add heart star settings-2') + console.error('Run with --help for more options') + process.exit(1) + } + + if (!fs.existsSync(lucideIconsDir)) { + console.error('❌ lucide-static not installed. Run pnpm install first.') + process.exit(1) + } + + let added = 0 + for (const iconId of iconIds) { + if (addIcon(iconId, overwrite)) { + added++ + } + } + + if (added > 0) { + console.log(`\n📦 Added ${added} icon(s). Run 'pnpm prepr:frontend:lib' to update exports.`) + } +} + +main() diff --git a/packages/assets/package.json b/packages/assets/package.json index 7d0a65ae6..8c5f12173 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -7,13 +7,16 @@ "scripts": { "lint": "pnpm run icons:validate && eslint . && prettier --check .", "fix": "pnpm run icons:generate && eslint . --fix && prettier --write .", + "icons:add": "jiti build/add-icons.ts", "icons:test": "jiti build/generate-exports.ts --test", "icons:validate": "jiti build/generate-exports.ts --validate", "icons:generate": "jiti build/generate-exports.ts" }, "devDependencies": { "@modrinth/tooling-config": "workspace:*", + "@types/node": "^20.1.0", "jiti": "^2.4.2", + "lucide-static": "^0.562.0", "vue": "^3.5.13" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 426871fb3..0a197629f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -428,9 +428,15 @@ importers: '@modrinth/tooling-config': specifier: workspace:* version: link:../tooling-config + '@types/node': + specifier: ^20.1.0 + version: 20.14.11 jiti: specifier: ^2.4.2 version: 2.4.2 + lucide-static: + specifier: ^0.562.0 + version: 0.562.0 vue: specifier: ^3.5.13 version: 3.5.13(typescript@5.8.3) @@ -5623,6 +5629,9 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-static@0.562.0: + resolution: {integrity: sha512-TM2vNVOEsO3+ijmno7n/VmxUo0Shr9OXC/UqZc5n4xEVyXX4E4NVvXoRPAZiSsIsdvlQ7alGOcIC/QGtR+OgUQ==} + magic-string-ast@0.6.2: resolution: {integrity: sha512-oN3Bcd7ZVt+0VGEs7402qR/tjgjbM7kPlH/z7ufJnzTLVBzXJITRHOJiwMmmYMgZfdoWQsfQcY+iKlxiBppnMA==} engines: {node: '>=16.14.0'} @@ -14324,6 +14333,8 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-static@0.562.0: {} + magic-string-ast@0.6.2: dependencies: magic-string: 0.30.14 From 7de4e55badd38aa969fb7181f5889e9f03ab757b Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Sat, 27 Dec 2025 00:24:08 +0000 Subject: [PATCH 011/158] feat: fix report msgs not showing (#4974) --- .../ui/moderation/ModerationReportCard.vue | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/components/ui/moderation/ModerationReportCard.vue b/apps/frontend/src/components/ui/moderation/ModerationReportCard.vue index 3b81942c1..5fe31f92d 100644 --- a/apps/frontend/src/components/ui/moderation/ModerationReportCard.vue +++ b/apps/frontend/src/components/ui/moderation/ModerationReportCard.vue @@ -31,9 +31,12 @@
- {{ - formatRelativeTime(report.created) - }} + + {{ formatRelativeTime(report.created) }} + + + diff --git a/apps/app-frontend/src/helpers/settings.ts b/apps/app-frontend/src/helpers/settings.ts index ae935224c..768a4f301 100644 --- a/apps/app-frontend/src/helpers/settings.ts +++ b/apps/app-frontend/src/helpers/settings.ts @@ -36,6 +36,7 @@ export type AppSettings = { max_concurrent_writes: number theme: ColorTheme + locale: string default_page: 'home' | 'library' collapsed_navigation: boolean hide_nametag_skins_page: boolean diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index f695cbca9..ed294cf0d 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -23,6 +23,9 @@ "app.settings.tabs.java-installations": { "message": "Java installations" }, + "app.settings.tabs.language": { + "message": "Language" + }, "app.settings.tabs.privacy": { "message": "Privacy" }, diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json index a9dadb039..8b7162151 100644 --- a/apps/frontend/src/locales/en-US/index.json +++ b/apps/frontend/src/locales/en-US/index.json @@ -2810,30 +2810,6 @@ "settings.display.theme.title": { "message": "Color theme" }, - "settings.language.categories.default": { - "message": "Standard languages" - }, - "settings.language.categories.search-result": { - "message": "Search results" - }, - "settings.language.description": { - "message": "Choose your preferred language for the site. Translations are contributed by volunteers on Crowdin." - }, - "settings.language.languages.automatic": { - "message": "Sync with the system language" - }, - "settings.language.languages.search-field.placeholder": { - "message": "Search for a language..." - }, - "settings.language.languages.search-results-announcement": { - "message": "{matches, plural, =0 {No languages match} one {# language matches} other {# languages match}} your search." - }, - "settings.language.languages.search.no-results": { - "message": "No languages match your search." - }, - "settings.language.warning": { - "message": "Changing the site language may cause some content to appear in English if a translation is not available. The site is not yet fully translated, so some content may remain in English for certain languages. We are still working on improving our localization system, so occasionally content may appear broken." - }, "settings.pats.action.create": { "message": "Create a PAT" }, diff --git a/apps/frontend/src/pages/settings.vue b/apps/frontend/src/pages/settings.vue index 3ef8db924..e1dfb9d4a 100644 --- a/apps/frontend/src/pages/settings.vue +++ b/apps/frontend/src/pages/settings.vue @@ -14,14 +14,12 @@ label: formatMessage(commonSettingsMessages.appearance), icon: PaintbrushIcon, }, - isStaging - ? { - link: '/settings/language', - label: formatMessage(commonSettingsMessages.language), - icon: LanguagesIcon, - badge: `${formatMessage(commonMessages.beta)}`, - } - : null, + { + link: '/settings/language', + label: formatMessage(commonSettingsMessages.language), + icon: LanguagesIcon, + badge: `${formatMessage(commonMessages.beta)}`, + }, auth.user ? { type: 'heading', label: 'Account' } : null, auth.user ? { @@ -103,5 +101,4 @@ const { formatMessage } = useVIntl() const route = useNativeRoute() const auth = await useAuth() -const isStaging = useRuntimeConfig().public.siteUrl !== 'https://modrinth.com' diff --git a/apps/frontend/src/pages/settings/language.vue b/apps/frontend/src/pages/settings/language.vue index b02cb25bc..1939659de 100644 --- a/apps/frontend/src/pages/settings/language.vue +++ b/apps/frontend/src/pages/settings/language.vue @@ -1,180 +1,31 @@ {{ $formatVersion(version.game_versions) }}
+
+

Environment

+
+ + + {{ environment.title.defaultMessage }} + +
+

Downloads

{{ version.downloads }} @@ -635,6 +646,7 @@ import { Checkbox, ConfirmModal, CopyCode, + ENVIRONMENTS_COPY, injectNotificationManager, MarkdownEditor, } from '@modrinth/ui' @@ -817,6 +829,12 @@ export default defineNuxtComponent({ if (!version) { version = props.versions.find((x) => x.displayUrlEnding === route.params.version) } + + const versionV3 = await useBaseFetch( + `project/${props.project.id}/version/${route.params.version}`, + { apiVersion: 3 }, + ) + if (versionV3) version.environment = versionV3.environment } if (!version) { @@ -933,6 +951,9 @@ export default defineNuxtComponent({ (a, b) => order.indexOf(a.dependency_type) - order.indexOf(b.dependency_type), ) }, + environment() { + return ENVIRONMENTS_COPY[this.version.environment] + }, }, watch: { '$route.path'() { diff --git a/apps/frontend/src/pages/dashboard/projects.vue b/apps/frontend/src/pages/dashboard/projects.vue index 2cc91eaa1..98a28399c 100644 --- a/apps/frontend/src/pages/dashboard/projects.vue +++ b/apps/frontend/src/pages/dashboard/projects.vue @@ -290,7 +290,7 @@ v-tooltip="'Please review environment metadata'" :to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${ project.slug ? project.slug : project.id - }/settings/environment`" + }?showEnvironmentMigrationWarning=true`" > diff --git a/packages/ui/src/components/project/ProjectPageVersions.vue b/packages/ui/src/components/project/ProjectPageVersions.vue index 3a4c16e62..c6b18bdf6 100644 --- a/packages/ui/src/components/project/ProjectPageVersions.vue +++ b/packages/ui/src/components/project/ProjectPageVersions.vue @@ -42,7 +42,12 @@
@@ -57,6 +62,12 @@ > Platforms
+
+ Environment +
@@ -144,6 +155,24 @@
+
+ + + {{ + ENVIRONMENTS_COPY[version.environment || 'unknown']?.title + ? formatMessage(ENVIRONMENTS_COPY[version.environment || 'unknown'].title) + : '' + }} + +
= computed( ) const selectedChannels: Ref = computed(() => versionFilters.value?.selectedChannels ?? []) +const hasMultipleEnvironments = computed(() => { + const environments = new Set(currentVersions.value.map((v) => v.environment).filter(Boolean)) + return environments.size > 1 +}) + const filteredVersions = computed(() => { return props.versions.filter( (version) => @@ -321,6 +357,14 @@ function updateQuery(newQueries: Record diff --git a/packages/ui/src/components/project/ProjectSidebarCompatibility.vue b/packages/ui/src/components/project/ProjectSidebarCompatibility.vue index 2b1b68190..a213cca65 100644 --- a/packages/ui/src/components/project/ProjectSidebarCompatibility.vue +++ b/packages/ui/src/components/project/ProjectSidebarCompatibility.vue @@ -21,7 +21,7 @@ :action="() => router.push(`/${project.project_type}s?g=categories:${platform}`)" :style="`--_color: var(--color-platform-${platform})`" > - + {{ formatCategory(platform) }}
@@ -69,6 +69,7 @@ +import { CheckIcon } from '@modrinth/assets' +import { + Admonition, + commonProjectSettingsMessages, + defineMessages, + EnvironmentSelector, + injectModrinthClient, + injectNotificationManager, + injectProjectPageContext, + UnsavedChangesPopup, + useSavable, + useVIntl, +} from '@modrinth/ui' +import { computed, ref } from 'vue' + +const { formatMessage } = useVIntl() + +const { currentMember, projectV2, projectV3, refreshProject } = injectProjectPageContext() +const { handleError } = injectNotificationManager() +const client = injectModrinthClient() + +const saving = ref(false) + +const supportsEnvironment = computed(() => + projectV3.value.project_types.some((type) => ['mod', 'modpack'].includes(type)), +) + +const needsToVerify = computed( + () => + projectV3.value.side_types_migration_review_status === 'pending' && + (projectV3.value.environment?.length ?? 0) > 0 && + projectV3.value.environment?.[0] !== 'unknown' && + supportsEnvironment.value, +) + +const hasPermission = computed(() => { + const EDIT_DETAILS = 1 << 2 + return (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS +}) + +function getInitialEnv() { + return projectV3.value.environment?.length === 1 ? projectV3.value.environment[0] : undefined +} + +const { saved, current, reset, save } = useSavable( + () => ({ + environment: getInitialEnv(), + side_types_migration_review_status: projectV3.value.side_types_migration_review_status, + }), + ({ environment, side_types_migration_review_status }) => { + saving.value = true + side_types_migration_review_status = 'reviewed' + client.labrinth.projects_v3 + .edit(projectV2.value.id, { environment, side_types_migration_review_status }) + .then(() => refreshProject().then(reset)) + .catch(handleError) + .finally(() => (saving.value = false)) + }, +) +// Set current to reviewed, which will trigger unsaved changes popup. +// It should not be possible to save without reviewing it. +const originalEnv = getInitialEnv() +if (originalEnv && originalEnv !== 'unknown') { + current.value.side_types_migration_review_status = 'reviewed' +} + +const messages = defineMessages({ + verifyButton: { + id: 'project.settings.environment.verification.verify-button', + defaultMessage: 'Verify', + }, + verifyLabel: { + id: 'project.settings.environment.verification.verify-text', + defaultMessage: `Verify that this project's environment is set correctly.`, + }, + wrongProjectTypeTitle: { + id: 'project.settings.environment.notice.wrong-project-type.title', + defaultMessage: `This project type does not support environment metadata`, + }, + wrongProjectTypeDescription: { + id: 'project.settings.environment.notice.wrong-project-type.description', + defaultMessage: `Only mod or modpack projects can have environment metadata.`, + }, + missingEnvTitle: { + id: 'project.settings.environment.notice.missing-env.title', + defaultMessage: `Please select an environment for your project`, + }, + missingEnvDescription: { + id: 'project.settings.environment.notice.missing-env.description', + defaultMessage: `Your project is missing environment metadata, please select the appropriate option below.`, + }, + multipleEnvironmentsTitle: { + id: 'project.settings.environment.notice.multiple-environments.title', + defaultMessage: 'Your project has multiple environments', + }, + multipleEnvironmentsDescription: { + id: 'project.settings.environment.notice.multiple-environments.description', + defaultMessage: + "Different versions of your project have different environments selected, so you can't edit them globally at this time.", + }, + reviewOptionsTitle: { + id: 'project.settings.environment.notice.review-options.title', + defaultMessage: 'Please review the options below', + }, + reviewOptionsDescription: { + id: 'project.settings.environment.notice.review-options.description', + defaultMessage: + "We've just overhauled the Environments system on Modrinth and new options are now available. Please ensure the correct option is selected below and then click 'Verify' when you're done!", + }, +}) + + diff --git a/packages/ui/src/components/project/settings/environment/ProjectSettingsEnvSelector.vue b/packages/ui/src/components/project/settings/environment/EnvironmentSelector.vue similarity index 98% rename from packages/ui/src/components/project/settings/environment/ProjectSettingsEnvSelector.vue rename to packages/ui/src/components/project/settings/environment/EnvironmentSelector.vue index 0c01a1a9a..f5ddc6c59 100644 --- a/packages/ui/src/components/project/settings/environment/ProjectSettingsEnvSelector.vue +++ b/packages/ui/src/components/project/settings/environment/EnvironmentSelector.vue @@ -33,7 +33,10 @@ const optionLabelFormat = defineMessage({ defaultMessage: '{title}: {description}', }) -const OUTER_OPTIONS = { +const OUTER_OPTIONS: Record< + string, + EnvironmentRadioOption & { suboptions: Record } +> = { client: { title: defineMessage({ id: 'project.settings.environment.client_only.title', @@ -125,10 +128,8 @@ const OUTER_OPTIONS = { }), suboptions: {}, }, -} as const satisfies Record< - string, - EnvironmentRadioOption & { suboptions: Record } -> +} as const + type OuterOptionKey = keyof typeof OUTER_OPTIONS type SubOptionKey = ValidKeys<(typeof OUTER_OPTIONS)[keyof typeof OUTER_OPTIONS]['suboptions']> @@ -248,7 +249,7 @@ const simulateSave = ref(false) :aria-label=" formatMessage(optionLabelFormat, { title: formatMessage(title), - description: formatMessage(description), + description: description ? formatMessage(description) : '', }) " @select=" diff --git a/packages/ui/src/components/project/settings/environment/ProjectEnvironmentModal.vue b/packages/ui/src/components/project/settings/environment/ProjectEnvironmentModal.vue new file mode 100644 index 000000000..f6e3b0871 --- /dev/null +++ b/packages/ui/src/components/project/settings/environment/ProjectEnvironmentModal.vue @@ -0,0 +1,40 @@ + + + diff --git a/packages/ui/src/components/project/settings/environment/environments.ts b/packages/ui/src/components/project/settings/environment/environments.ts new file mode 100644 index 000000000..cc67c4789 --- /dev/null +++ b/packages/ui/src/components/project/settings/environment/environments.ts @@ -0,0 +1,129 @@ +import type { Labrinth } from '@modrinth/api-client' +import { ClientIcon, MonitorSmartphoneIcon, ServerIcon, UserIcon } from '@modrinth/assets' +import type { Component } from 'vue' + +import { defineMessage, type MessageDescriptor } from '../../../../composables/i18n' + +export const ENVIRONMENTS_COPY: Record< + Labrinth.Projects.v3.Environment, + { title: MessageDescriptor; description: MessageDescriptor; icon?: Component } +> = { + client_only: { + title: defineMessage({ + id: 'project.environment.client-only.title', + defaultMessage: 'Client-side only', + }), + description: defineMessage({ + id: 'project.environment.client-only.description', + defaultMessage: + 'All functionality is done client-side and is compatible with vanilla servers.', + }), + icon: ClientIcon, + }, + server_only: { + title: defineMessage({ + id: 'project.environment.server-only.title', + defaultMessage: 'Server-side only', + }), + description: defineMessage({ + id: 'project.environment.server-only.description', + defaultMessage: + 'All functionality is done server-side and is compatible with vanilla clients.', + }), + icon: ServerIcon, + }, + singleplayer_only: { + title: defineMessage({ + id: 'project.environment.singleplayer-only.title', + defaultMessage: 'Singleplayer only', + }), + description: defineMessage({ + id: 'project.environment.singleplayer-only.description', + defaultMessage: + 'Only functions in Singleplayer or when not connected to a Multiplayer server.', + }), + icon: UserIcon, + }, + dedicated_server_only: { + title: defineMessage({ + id: 'project.environment.dedicated-server-only.title', + defaultMessage: 'Server-side only', + }), + description: defineMessage({ + id: 'project.environment.dedicated-server-only.description', + defaultMessage: + 'All functionality is done server-side and is compatible with vanilla clients.', + }), + icon: ServerIcon, + }, + client_and_server: { + title: defineMessage({ + id: 'project.environment.client-and-server.title', + defaultMessage: 'Client and server', + }), + description: defineMessage({ + id: 'project.environment.client-and-server.description', + defaultMessage: + 'Has some functionality on both the client and server, even if only partially.', + }), + icon: MonitorSmartphoneIcon, + }, + client_only_server_optional: { + title: defineMessage({ + id: 'project.environment.client-only-server-optional.title', + defaultMessage: 'Client and server', + }), + description: defineMessage({ + id: 'project.environment.client-only-server-optional.description', + defaultMessage: + 'Has some functionality on both the client and server, even if only partially.', + }), + icon: MonitorSmartphoneIcon, + }, + server_only_client_optional: { + title: defineMessage({ + id: 'project.environment.server-only-client-optional.title', + defaultMessage: 'Client and server', + }), + description: defineMessage({ + id: 'project.environment.server-only-client-optional.description', + defaultMessage: + 'Has some functionality on both the client and server, even if only partially.', + }), + icon: MonitorSmartphoneIcon, + }, + client_or_server: { + title: defineMessage({ + id: 'project.environment.client-or-server.title', + defaultMessage: 'Client and server', + }), + description: defineMessage({ + id: 'project.environment.client-or-server.description', + defaultMessage: + 'Has some functionality on both the client and server, even if only partially.', + }), + icon: MonitorSmartphoneIcon, + }, + client_or_server_prefers_both: { + title: defineMessage({ + id: 'project.environment.client-or-server-prefers-both.title', + defaultMessage: 'Client and server', + }), + description: defineMessage({ + id: 'project.environment.client-or-server-prefers-both.description', + defaultMessage: + 'Has some functionality on both the client and server, even if only partially.', + }), + icon: MonitorSmartphoneIcon, + }, + unknown: { + title: defineMessage({ + id: 'project.environment.unknown.title', + defaultMessage: 'Unknown environment', + }), + description: defineMessage({ + id: 'project.environment.unknown.description', + defaultMessage: 'The environment for this version could not be determined.', + }), + }, +} diff --git a/packages/ui/src/components/project/settings/index.ts b/packages/ui/src/components/project/settings/index.ts index dc2fb0c13..d538f8adf 100644 --- a/packages/ui/src/components/project/settings/index.ts +++ b/packages/ui/src/components/project/settings/index.ts @@ -1,2 +1,4 @@ -// Environment -export { default as ProjectSettingsEnvSelector } from './environment/ProjectSettingsEnvSelector.vue' +export { default as EnvironmentMigration } from './environment/EnvironmentMigration.vue' +export { ENVIRONMENTS_COPY } from './environment/environments' +export { default as EnvironmentSelector } from './environment/EnvironmentSelector.vue' +export { default as ProjectEnvironmentModal } from './environment/ProjectEnvironmentModal.vue' diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json index ab0a23ebe..e48de6d9d 100644 --- a/packages/ui/src/locales/en-US/index.json +++ b/packages/ui/src/locales/en-US/index.json @@ -677,6 +677,66 @@ "project.about.links.wiki": { "defaultMessage": "Visit wiki" }, + "project.environment.client-and-server.description": { + "defaultMessage": "Has some functionality on both the client and server, even if only partially." + }, + "project.environment.client-and-server.title": { + "defaultMessage": "Client and server" + }, + "project.environment.client-only-server-optional.description": { + "defaultMessage": "Has some functionality on both the client and server, even if only partially." + }, + "project.environment.client-only-server-optional.title": { + "defaultMessage": "Client and server" + }, + "project.environment.client-only.description": { + "defaultMessage": "All functionality is done client-side and is compatible with vanilla servers." + }, + "project.environment.client-only.title": { + "defaultMessage": "Client-side only" + }, + "project.environment.client-or-server-prefers-both.description": { + "defaultMessage": "Has some functionality on both the client and server, even if only partially." + }, + "project.environment.client-or-server-prefers-both.title": { + "defaultMessage": "Client and server" + }, + "project.environment.client-or-server.description": { + "defaultMessage": "Has some functionality on both the client and server, even if only partially." + }, + "project.environment.client-or-server.title": { + "defaultMessage": "Client and server" + }, + "project.environment.dedicated-server-only.description": { + "defaultMessage": "All functionality is done server-side and is compatible with vanilla clients." + }, + "project.environment.dedicated-server-only.title": { + "defaultMessage": "Server-side only" + }, + "project.environment.server-only-client-optional.description": { + "defaultMessage": "Has some functionality on both the client and server, even if only partially." + }, + "project.environment.server-only-client-optional.title": { + "defaultMessage": "Client and server" + }, + "project.environment.server-only.description": { + "defaultMessage": "All functionality is done server-side and is compatible with vanilla clients." + }, + "project.environment.server-only.title": { + "defaultMessage": "Server-side only" + }, + "project.environment.singleplayer-only.description": { + "defaultMessage": "Only functions in Singleplayer or when not connected to a Multiplayer server." + }, + "project.environment.singleplayer-only.title": { + "defaultMessage": "Singleplayer only" + }, + "project.environment.unknown.description": { + "defaultMessage": "The environment for this version could not be determined." + }, + "project.environment.unknown.title": { + "defaultMessage": "Unknown environment" + }, "project.settings.analytics.title": { "defaultMessage": "Analytics" }, @@ -710,6 +770,30 @@ "project.settings.environment.client_only.title": { "defaultMessage": "Client-side only" }, + "project.settings.environment.notice.missing-env.description": { + "defaultMessage": "Your project is missing environment metadata, please select the appropriate option below." + }, + "project.settings.environment.notice.missing-env.title": { + "defaultMessage": "Please select an environment for your project" + }, + "project.settings.environment.notice.multiple-environments.description": { + "defaultMessage": "Different versions of your project have different environments selected, so you can't edit them globally at this time." + }, + "project.settings.environment.notice.multiple-environments.title": { + "defaultMessage": "Your project has multiple environments" + }, + "project.settings.environment.notice.review-options.description": { + "defaultMessage": "We've just overhauled the Environments system on Modrinth and new options are now available. Please ensure the correct option is selected below and then click 'Verify' when you're done!" + }, + "project.settings.environment.notice.review-options.title": { + "defaultMessage": "Please review the options below" + }, + "project.settings.environment.notice.wrong-project-type.description": { + "defaultMessage": "Only mod or modpack projects can have environment metadata." + }, + "project.settings.environment.notice.wrong-project-type.title": { + "defaultMessage": "This project type does not support environment metadata" + }, "project.settings.environment.server_only.dedicated_only.title": { "defaultMessage": "Dedicated server only" }, @@ -737,6 +821,12 @@ "project.settings.environment.title": { "defaultMessage": "Environment" }, + "project.settings.environment.verification.verify-button": { + "defaultMessage": "Verify" + }, + "project.settings.environment.verification.verify-text": { + "defaultMessage": "Verify that this project's environment is set correctly." + }, "project.settings.gallery.title": { "defaultMessage": "Gallery" }, From 366a0a636669d2724c319ecc1fed7f53f6fb1a8a Mon Sep 17 00:00:00 2001 From: Prospector <6166773+Prospector@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:33:57 -0800 Subject: [PATCH 023/158] changelog --- packages/utils/changelog.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/utils/changelog.ts b/packages/utils/changelog.ts index 99223b6fc..2a2d87e65 100644 --- a/packages/utils/changelog.ts +++ b/packages/utils/changelog.ts @@ -10,6 +10,18 @@ export type VersionEntry = { } const VERSIONS: VersionEntry[] = [ + { + date: `2025-12-29T14:40:00-08:00`, + product: 'web', + body: `## Added +- Language changer, now available in beta. +## Improvements +- Updated DMCA agent info. +- Fixed withdraw history to show the same information as when you are withdrawing. +- Fixed authorizations page not loading. +- Fixed withdrawing to a company bank account not working when owner info is needed. +- Fixed various issues with version environments.`, + }, { date: `2025-12-22T14:20:00-08:00`, product: 'web', From 1a16d61511d734c13194590f1c07c4378f679d77 Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Mon, 29 Dec 2025 23:40:56 +0000 Subject: [PATCH 024/158] fix: rev page broken (#4994) * fix: i18n * fix: lint --- .../src/components/ui/dashboard/RevenueTransaction.vue | 1 + apps/frontend/src/pages/[type]/[id]/index.vue | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/ui/dashboard/RevenueTransaction.vue b/apps/frontend/src/components/ui/dashboard/RevenueTransaction.vue index fde61c060..2e51ab13c 100644 --- a/apps/frontend/src/components/ui/dashboard/RevenueTransaction.vue +++ b/apps/frontend/src/components/ui/dashboard/RevenueTransaction.vue @@ -79,6 +79,7 @@ import { ButtonStyled, getCurrencyIcon, injectNotificationManager, + useVIntl, } from '@modrinth/ui' import { capitalizeString, formatMoney } from '@modrinth/utils' import dayjs from 'dayjs' diff --git a/apps/frontend/src/pages/[type]/[id]/index.vue b/apps/frontend/src/pages/[type]/[id]/index.vue index cb4de89e0..7f3c08c7b 100644 --- a/apps/frontend/src/pages/[type]/[id]/index.vue +++ b/apps/frontend/src/pages/[type]/[id]/index.vue @@ -28,13 +28,13 @@ defineProps({ versions: { type: Array, default() { - return {} + return [] }, }, members: { type: Array, default() { - return {} + return [] }, }, organization: { From b07a1659b444be2f52dca7ab030f2fa4517a0a73 Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Tue, 30 Dec 2025 15:06:52 +0000 Subject: [PATCH 025/158] chore: update to nuxt 3.20 (#4992) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: nuxt 3.14 → 3.15.4 * feat: nuxt 3.15.4 → 3.16.2 (vite 6) * feat: bump nuxt-i18n * feat: nuxt 3.20 * fix: lint * feat: use rolldown-vite * fix: shut the fuck up * fix: silence for app as well * fix: vue-router mismatch --------- Signed-off-by: Calum H. --- apps/app-frontend/package.json | 10 +- .../components/ui/InstanceCreationModal.vue | 4 +- .../src/components/ui/ProjectCard.vue | 2 +- .../src/components/ui/SplashScreen.vue | 3 +- .../src/components/ui/world/WorldItem.vue | 4 +- apps/app-frontend/vite.config.ts | 8 + apps/frontend/{src => i18n}/i18n.config.ts | 10 +- apps/frontend/nuxt.config.ts | 38 +- apps/frontend/package.json | 17 +- apps/frontend/src/app.vue | 1 + apps/frontend/src/plugins/floating-vue.js | 20 - package.json | 9 + packages/api-client/src/features/auth.ts | 2 +- .../src/features/circuit-breaker.ts | 2 +- packages/api-client/src/features/retry.ts | 2 +- packages/api-client/src/platform/nuxt.ts | 2 +- packages/api-client/src/platform/tauri.ts | 2 +- packages/assets/styles/classes.scss | 14 +- packages/assets/styles/defaults.scss | 5 +- packages/assets/styles/variables.scss | 9 +- .../moderation/src/data/nags/description.ts | 24 +- packages/moderation/src/data/stages/links.ts | 8 +- packages/tooling-config/eslint/nuxt.mjs | 1 + packages/tooling-config/package.json | 2 + packages/ui/package.json | 6 +- .../billing/ServersUpgradeModalWrapper.vue | 4 +- .../ui/src/components/skin/SkinButton.vue | 3 +- .../components/skin/SkinLikeTextButton.vue | 3 +- .../components/skin/SkinPreviewRenderer.vue | 7 +- packages/ui/src/composables/i18n.ts | 5 +- pnpm-lock.yaml | 13193 ++++++++-------- 31 files changed, 6760 insertions(+), 6660 deletions(-) rename apps/frontend/{src => i18n}/i18n.config.ts (73%) delete mode 100644 apps/frontend/src/plugins/floating-vue.js diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index a0468e289..c7561a588 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -29,18 +29,18 @@ "@tauri-apps/plugin-window-state": "^2.2.2", "@types/three": "^0.172.0", "intl-messageformat": "^10.7.7", - "vue-i18n": "^9.14.0", + "vue-i18n": "^10.0.0", "@vueuse/core": "^11.1.0", "dayjs": "^1.11.10", "floating-vue": "^5.2.2", "ofetch": "^1.3.4", - "pinia": "^2.1.7", + "pinia": "^3.0.0", "posthog-js": "^1.158.2", "three": "^0.172.0", "vite-svg-loader": "^5.1.0", "vue": "^3.5.13", "vue-multiselect": "3.0.0", - "vue-router": "4.3.0", + "vue-router": "^4.6.0", "vue-virtual-scroller": "v2.0.0-beta.8" }, "devDependencies": { @@ -49,7 +49,7 @@ "@modrinth/tooling-config": "workspace:*", "@nuxt/eslint-config": "^0.5.6", "@taijased/vue-render-tracker": "^1.0.7", - "@vitejs/plugin-vue": "^5.0.4", + "@vitejs/plugin-vue": "^6.0.3", "autoprefixer": "^10.4.19", "eslint": "^9.9.1", "eslint-plugin-turbo": "^2.5.4", @@ -58,7 +58,7 @@ "sass": "^1.74.1", "tailwindcss": "^3.4.4", "typescript": "^5.5.4", - "vite": "^5.4.6", + "vite": "^6.0.0", "vue-component-type-helpers": "^3.1.8", "vue-tsc": "^2.1.6" }, diff --git a/apps/app-frontend/src/components/ui/InstanceCreationModal.vue b/apps/app-frontend/src/components/ui/InstanceCreationModal.vue index 83dee767f..a8158f07c 100644 --- a/apps/app-frontend/src/components/ui/InstanceCreationModal.vue +++ b/apps/app-frontend/src/components/ui/InstanceCreationModal.vue @@ -341,7 +341,7 @@ const create_instance = async () => { creating.value = true const loader_version_value = loader_version.value === 'other' ? specified_loader_version.value : loader_version.value - const loaderVersion = loader.value === 'vanilla' ? null : loader_version_value ?? 'stable' + const loaderVersion = loader.value === 'vanilla' ? null : (loader_version_value ?? 'stable') hide() creating.value = false @@ -350,7 +350,7 @@ const create_instance = async () => { profile_name.value, game_version.value, loader.value, - loader.value === 'vanilla' ? null : loader_version_value ?? 'stable', + loader.value === 'vanilla' ? null : (loader_version_value ?? 'stable'), icon.value, ).catch(handleError) diff --git a/apps/app-frontend/src/components/ui/ProjectCard.vue b/apps/app-frontend/src/components/ui/ProjectCard.vue index 2d23f3ae3..43e9a10df 100644 --- a/apps/app-frontend/src/components/ui/ProjectCard.vue +++ b/apps/app-frontend/src/components/ui/ProjectCard.vue @@ -63,7 +63,7 @@ const toTransparent = computed(() => {
diff --git a/apps/app-frontend/vite.config.ts b/apps/app-frontend/vite.config.ts index ac1c29a04..3b35e2df6 100644 --- a/apps/app-frontend/vite.config.ts +++ b/apps/app-frontend/vite.config.ts @@ -9,6 +9,14 @@ const projectRootDir = resolve(__dirname) // https://vitejs.dev/config/ export default defineConfig({ + css: { + preprocessorOptions: { + scss: { + // TODO: dont forget about this + silenceDeprecations: ['import'], + }, + }, + }, resolve: { alias: [ { diff --git a/apps/frontend/src/i18n.config.ts b/apps/frontend/i18n/i18n.config.ts similarity index 73% rename from apps/frontend/src/i18n.config.ts rename to apps/frontend/i18n/i18n.config.ts index 71f594342..6810b4bcd 100644 --- a/apps/frontend/src/i18n.config.ts +++ b/apps/frontend/i18n/i18n.config.ts @@ -1,8 +1,11 @@ import { buildLocaleMessages, createMessageCompiler, type CrowdinMessages } from '@modrinth/ui' -const localeModules = import.meta.glob<{ default: CrowdinMessages }>('./locales/*/index.json', { - eager: true, -}) +const localeModules = import.meta.glob<{ default: CrowdinMessages }>( + '../src/locales/*/index.json', + { + eager: true, + }, +) export default defineI18nConfig(() => ({ legacy: false, @@ -11,5 +14,6 @@ export default defineI18nConfig(() => ({ messageCompiler: createMessageCompiler(), missingWarn: false, fallbackWarn: false, + // @ts-expect-error - buildLocaleMessages returns compatible format at runtime messages: buildLocaleMessages(localeModules), })) diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts index 472f5bb6f..d73794de8 100644 --- a/apps/frontend/nuxt.config.ts +++ b/apps/frontend/nuxt.config.ts @@ -56,6 +56,18 @@ export default defineNuxtConfig({ }, }, vite: { + css: { + preprocessorOptions: { + scss: { + // TODO: dont forget about this + silenceDeprecations: ['import'], + }, + }, + }, + ssr: { + // https://github.com/Akryum/floating-vue/issues/809#issuecomment-1002996240 + noExternal: ['v-tooltip'], + }, define: { global: {}, }, @@ -196,9 +208,24 @@ export default defineNuxtConfig({ }, }, }, - modules: ['@nuxtjs/i18n', '@pinia/nuxt'], + modules: ['@nuxtjs/i18n', '@pinia/nuxt', 'floating-vue/nuxt'], + floatingVue: { + themes: { + 'ribbit-popout': { + $extend: 'dropdown', + placement: 'bottom-end', + instantMove: true, + distance: 8, + }, + 'dismissable-prompt': { + $extend: 'dropdown', + placement: 'bottom-start', + }, + }, + }, i18n: { defaultLocale: 'en-US', + // @ts-expect-error - LocaleDefinition is compatible at runtime locales: LOCALES, strategy: 'no_prefix', detectBrowserLanguage: { @@ -206,11 +233,14 @@ export default defineNuxtConfig({ cookieKey: 'locale', fallbackLocale: 'en-US', }, - vueI18n: './src/i18n.config.ts', + vueI18n: './i18n.config.ts', + bundle: { + optimizeTranslationDirective: false, + }, }, nitro: { rollupConfig: { - // @ts-expect-error it's not infinite. + // @ts-expect-error because of rolldown-vite - completely fine though plugins: [serverSidedVue()], }, }, @@ -255,7 +285,7 @@ export default defineNuxtConfig({ }, }, }, - compatibilityDate: '2024-07-03', + compatibilityDate: '2025-01-01', telemetry: false, }) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 803e69509..e0720e9a5 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -10,20 +10,19 @@ "postinstall": "nuxi prepare", "lint": "eslint . && prettier --check .", "fix": "eslint . --fix && prettier --write .", - "intl:extract": "formatjs extract \"{,src/components,src/composables,src/layouts,src/middleware,src/modules,src/pages,src/plugins,src/utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" \"src/error.vue\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace", + "intl:extract": "formatjs extract \"src/{components,composables,layouts,middleware,modules,pages,plugins,utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" \"src/error.vue\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace", "test": "nuxi build" }, "devDependencies": { "@formatjs/cli": "^6.2.12", - "@nuxt/devtools": "^1.3.3", - "@nuxtjs/i18n": "^8.5.5", + "@modrinth/tooling-config": "workspace:*", + "@nuxtjs/i18n": "^9.0.0", "@types/dompurify": "^3.0.5", "@types/iso-3166-2": "^1.0.4", "@types/node": "^20.1.0", "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", "glob": "^10.2.7", - "nuxt": "^3.14.1592", + "nuxt": "^3.20.2", "postcss": "^8.4.39", "prettier-plugin-tailwindcss": "^0.6.5", "sass": "^1.58.0", @@ -43,11 +42,10 @@ "@modrinth/moderation": "workspace:*", "@modrinth/ui": "workspace:*", "@modrinth/utils": "workspace:*", - "@pinia/nuxt": "^0.5.1", + "@pinia/nuxt": "^0.11.3", "@tanstack/vue-query": "^5.90.7", "@types/three": "^0.172.0", - "intl-messageformat": "^10.7.7", - "@vitejs/plugin-vue": "^5.0.4", + "@vitejs/plugin-vue": "^6.0.3", "@vue-email/components": "^0.0.21", "@vue-email/render": "^0.0.9", "@vueuse/core": "^11.1.0", @@ -58,12 +56,13 @@ "floating-vue": "^5.2.2", "fuse.js": "^6.6.2", "highlight.js": "^11.7.0", + "intl-messageformat": "^10.7.7", "iso-3166-2": "1.0.0", "js-yaml": "^4.1.0", "jszip": "^3.10.1", "markdown-it": "14.1.0", "pathe": "^1.1.2", - "pinia": "^2.1.7", + "pinia": "^3.0.0", "pinia-plugin-persistedstate": "^4.4.1", "prettier": "^3.6.2", "qrcode.vue": "^3.4.0", diff --git a/apps/frontend/src/app.vue b/apps/frontend/src/app.vue index a25d0e634..2cf651352 100644 --- a/apps/frontend/src/app.vue +++ b/apps/frontend/src/app.vue @@ -1,5 +1,6 @@