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 060333fe..7d5df72c 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 74eb943f..aec40a1b 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 b7447ee4..e6422042 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 aec40a1b..99223b6f 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 af174464..caa8bc1d 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 6d8135de..d3dece98 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 ec395089..ebda1791 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 9931da62..a2ad0196 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 00000000..357ed9e8 --- /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 00000000..d2a51e97 --- /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 8d43b804..00000000 --- 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 a90b2eeb..b2a4215d 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 337b7f2e..e6174116 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 8bd77fb4..a5524b39 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 68f560a1..5fb20629 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 cb02f8c3..249e457f 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 ad40ec7b..5e7eb946 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 5e6efdab..2d3655e4 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 e5d1fe38..c69c3bbc 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 c69c3bbc..e5d1fe38 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 d3323e11..e81246e2 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 69bf2f16..dd57bbcd 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 e4e7c66f..0839c157 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 f73ed47d..3c6ca2a6 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 1160b182..00000000 --- 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 00000000..5ee87de4 --- /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 ba62ac5a..b0d4e4ad 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 0ab19bc5..42161ddf 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 f9c7d234..6ae91541 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 b9aeb835..aec2b343 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 6ae91541..94b2206a 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 00000000..ed548055 --- /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 7d0a65ae..8c5f1217 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 426871fb..0a197629 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 3b81942c..5fe31f92 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 ae935224..768a4f30 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 f695cbca..ed294cf0 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 a9dadb03..8b716215 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 3ef8db92..e1dfb9d4 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 b02cb25b..1939659d 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 2cc91eaa..98a28399 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 3a4c16e6..c6b18bdf 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 2b1b6819..a213cca6 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 0c01a1a9..f5ddc6c5 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 00000000..f6e3b087 --- /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 00000000..cc67c478 --- /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 dc2fb0c1..d538f8ad 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 ab0a23eb..e48de6d9 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 99223b6f..2a2d87e6 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 fde61c06..2e51ab13 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 cb4de89e..7f3c08c7 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 a0468e28..c7561a58 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 83dee767..a8158f07 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 2d23f3ae..43e9a10d 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 ac1c29a0..3b35e2df 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 71f59434..6810b4bc 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 472f5bb6..d73794de 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 803e6950..e0720e9a 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 a25d0e63..2cf65135 100644 --- a/apps/frontend/src/app.vue +++ b/apps/frontend/src/app.vue @@ -1,5 +1,6 @@