-
+
Moderator
@@ -69,6 +86,17 @@
closed the thread.reopened the thread.
+
+ completed technical review and marked project as
+ .
+
+
+ The project has entered the technical review queue.
+
+
+ The project has left the technical review queue as all files pending review were deleted by
+ the user.
+
Find a modpack. Now it's a server.
@@ -148,7 +148,7 @@
Play where your mods are
- Modrinth Servers seamlessly integrates the mod and modpack installation process into
+ Modrinth Hosting seamlessly integrates the mod and modpack installation process into
your server.
@@ -213,7 +213,7 @@
Experience modern, reliable hosting
- Modrinth Servers are hosted on
+ Modrinth Hosting servers are hosted on
high-performance AMD CPUs with DDR5 RAM, running on
custom-built software to ensure your server performs smoothly.
@@ -341,7 +341,7 @@
A powerful console, server properties manager, and more
- Modrinth Servers come with powerful tools to manage your server.
+ Modrinth Hosting comes with powerful tools to manage your server.
@@ -378,7 +378,7 @@
SFTP access
- Access your server files directly with SFTP built into Modrinth Servers.
+ Access your server's files directly with SFTP built into Modrinth Hosting.
@@ -398,11 +398,11 @@
- What kind of CPUs do Modrinth Servers run on?
+ What kind of CPUs do Modrinth Hosting servers run on?
- Modrinth Servers are powered by AMD Ryzen 7900 and 7950X3D equivalent CPUs at 5+
- GHz, paired with DDR5 memory.
+ Modrinth Hosting servers are powered by AMD Ryzen 7900 and 7950X3D equivalent CPUs
+ at 5+ GHz, paired with DDR5 memory.
@@ -426,11 +426,11 @@
- Do Modrinth Servers have DDoS protection?
+ Do Modrinth Hosting servers have DDoS protection?
- Yes. All Modrinth Servers come with DDoS protection, with up to 17Tbps capacity in
- some locations.
+ Yes. All Modrinth Hosting servers come with DDoS protection, with up to 17Tbps
+ capacity in some locations.
@@ -439,7 +439,7 @@
- Where are Modrinth Servers located? Can I choose a region?
+ Where are Modrinth Hosting servers located? Can I choose a region?
We have servers available in North America, Europe, and Southeast Asia at the moment
@@ -466,13 +466,13 @@
- How fast are Modrinth Servers?
+ How fast are Modrinth Hosting servers?
- Modrinth Servers are hosted on very modern high-performance hardware, but it's tough
- to say how exactly that will translate into how fast your server will run because
- there are so many factors that affect it, such as the mods, data packs, or plugins
- you're running on your server, and even user behavior.
+ Modrinth Hosting servers are hosted on very modern high-performance hardware, but
+ it's tough to say how exactly that will translate into how fast your server will run
+ because there are so many factors that affect it, such as the mods, data packs, or
+ plugins you're running on your server, and even user behavior.
Most performance issues that arise tend to be the fault of an unoptimized modpack,
@@ -502,8 +502,8 @@
What Minecraft versions and loaders can be used?
- Modrinth Servers can run any version of Minecraft: Java Edition going all the way
- back to version 1.2.5, including snapshot versions.
+ Modrinth Hosting servers can run any version of Minecraft: Java Edition going all
+ the way back to version 1.2.5, including snapshot versions.
We also support a wide range of mod and plugin loaders, including Fabric, Quilt,
@@ -705,7 +705,7 @@ const lowestPrice = computed(() => {
return amount ? amount / monthsInInterval[billingPeriod.value] : undefined
})
-const title = 'Modrinth Servers'
+const title = 'Modrinth Hosting'
const description =
'Start your own Minecraft server directly on Modrinth. Play your favorite mods, plugins, and datapacks — without the hassle of setup.'
diff --git a/apps/frontend/src/pages/servers/manage/[id].vue b/apps/frontend/src/pages/hosting/manage/[id].vue
similarity index 79%
rename from apps/frontend/src/pages/servers/manage/[id].vue
rename to apps/frontend/src/pages/hosting/manage/[id].vue
index 7a04ce38..af174464 100644
--- a/apps/frontend/src/pages/servers/manage/[id].vue
+++ b/apps/frontend/src/pages/hosting/manage/[id].vue
@@ -1,7 +1,7 @@
+
+
+
-
+
All servers
@@ -231,7 +243,7 @@
If this version of Minecraft was released recently, please check if Modrinth
- Servers supports it.
+ Hosting supports it.
If you've installed a modpack, it may have been packaged incorrectly or may
@@ -261,7 +273,7 @@
- An error occurred while installing your server because Modrinth Servers does not
+ An error occurred while installing your server because Modrinth Hosting does not
support the version of Minecraft or the loader you specified. Try reinstalling
your server with a different version or loader, and if the problem persists,
please contact Modrinth Support with your server's debug information.
@@ -284,7 +296,7 @@
Change Loader
@@ -341,7 +353,6 @@
:stats="stats"
:server-power-state="serverPowerState"
:power-state-details="powerStateDetails"
- :socket="socket"
:server="server"
:backup-in-progress="backupInProgress"
@reinstall="onReinstall"
@@ -362,6 +373,7 @@
+
+
+
+
diff --git a/apps/frontend/src/pages/servers/manage/[id]/content.vue b/apps/frontend/src/pages/hosting/manage/[id]/content.vue
similarity index 100%
rename from apps/frontend/src/pages/servers/manage/[id]/content.vue
rename to apps/frontend/src/pages/hosting/manage/[id]/content.vue
diff --git a/apps/frontend/src/pages/servers/manage/[id]/content/index.vue b/apps/frontend/src/pages/hosting/manage/[id]/content/index.vue
similarity index 96%
rename from apps/frontend/src/pages/servers/manage/[id]/content/index.vue
rename to apps/frontend/src/pages/hosting/manage/[id]/content/index.vue
index 80b1fc7c..9126c5e7 100644
--- a/apps/frontend/src/pages/servers/manage/[id]/content/index.vue
+++ b/apps/frontend/src/pages/hosting/manage/[id]/content/index.vue
@@ -90,7 +90,7 @@
Add {{ type.toLocaleLowerCase() }}
@@ -127,7 +127,7 @@
width: '100%',
}"
>
-
+
+ (preview ? {} : emit('dismiss'))"
>
-
+ Dismiss
@@ -91,6 +91,12 @@ const NOTICE_TYPE: Record = {
critical: 'critical',
}
+const NOTICE_TYPE_BTN: Record = {
+ info: 'blue',
+ warn: 'orange',
+ critical: 'red',
+}
+
const heading = computed(() => NOTICE_HEADINGS[props.level] ?? messages.info)
diff --git a/packages/ui/src/components/base/index.ts b/packages/ui/src/components/base/index.ts
new file mode 100644
index 00000000..71ae8c64
--- /dev/null
+++ b/packages/ui/src/components/base/index.ts
@@ -0,0 +1,61 @@
+export { default as Accordion } from './Accordion.vue'
+export { default as Admonition } from './Admonition.vue'
+export { default as AppearingProgressBar } from './AppearingProgressBar.vue'
+export { default as AutoBrandIcon } from './AutoBrandIcon.vue'
+export { default as AutoLink } from './AutoLink.vue'
+export { default as Avatar } from './Avatar.vue'
+export { default as Badge } from './Badge.vue'
+export { default as BulletDivider } from './BulletDivider.vue'
+export { default as Button } from './Button.vue'
+export { default as ButtonStyled } from './ButtonStyled.vue'
+export { default as Card } from './Card.vue'
+export { default as Checkbox } from './Checkbox.vue'
+export { default as Chips } from './Chips.vue'
+export { default as Collapsible } from './Collapsible.vue'
+export { default as CollapsibleRegion } from './CollapsibleRegion.vue'
+export type { ComboboxOption } from './Combobox.vue'
+export { default as Combobox } from './Combobox.vue'
+export { default as ContentPageHeader } from './ContentPageHeader.vue'
+export { default as CopyCode } from './CopyCode.vue'
+export { default as DoubleIcon } from './DoubleIcon.vue'
+export { default as DropArea } from './DropArea.vue'
+export { default as DropdownSelect } from './DropdownSelect.vue'
+export { default as DropzoneFileInput } from './DropzoneFileInput.vue'
+export { default as EnvironmentIndicator } from './EnvironmentIndicator.vue'
+export { default as ErrorInformationCard } from './ErrorInformationCard.vue'
+export { default as FileInput } from './FileInput.vue'
+export type { FilterBarOption } from './FilterBar.vue'
+export { default as FilterBar } from './FilterBar.vue'
+export { default as HeadingLink } from './HeadingLink.vue'
+export { default as HorizontalRule } from './HorizontalRule.vue'
+export { default as IconSelect } from './IconSelect.vue'
+export type { JoinedButtonAction } from './JoinedButtons.vue'
+export { default as JoinedButtons } from './JoinedButtons.vue'
+export { default as LoadingIndicator } from './LoadingIndicator.vue'
+export { default as ManySelect } from './ManySelect.vue'
+export { default as MarkdownEditor } from './MarkdownEditor.vue'
+export type { MaybeCtxFn, StageButtonConfig, StageConfigInput } from './MultiStageModal.vue'
+export { default as MultiStageModal } from './MultiStageModal.vue'
+export { resolveCtxFn } from './MultiStageModal.vue'
+export { default as OptionGroup } from './OptionGroup.vue'
+export type { Option as OverflowMenuOption } from './OverflowMenu.vue'
+export { default as OverflowMenu } from './OverflowMenu.vue'
+export { default as Page } from './Page.vue'
+export { default as Pagination } from './Pagination.vue'
+export { default as PopoutMenu } from './PopoutMenu.vue'
+export { default as PreviewSelectButton } from './PreviewSelectButton.vue'
+export { default as ProgressBar } from './ProgressBar.vue'
+export { default as ProgressSpinner } from './ProgressSpinner.vue'
+export { default as ProjectCard } from './ProjectCard.vue'
+export { default as RadialHeader } from './RadialHeader.vue'
+export { default as RadioButtons } from './RadioButtons.vue'
+export { default as ScrollablePanel } from './ScrollablePanel.vue'
+export { default as ServerNotice } from './ServerNotice.vue'
+export { default as SettingsLabel } from './SettingsLabel.vue'
+export { default as SimpleBadge } from './SimpleBadge.vue'
+export { default as Slider } from './Slider.vue'
+export { default as SmartClickable } from './SmartClickable.vue'
+export { default as TagItem } from './TagItem.vue'
+export { default as Timeline } from './Timeline.vue'
+export { default as Toggle } from './Toggle.vue'
+export { default as UnsavedChangesPopup } from './UnsavedChangesPopup.vue'
diff --git a/packages/ui/src/components/billing/FormattedPaymentMethod.vue b/packages/ui/src/components/billing/FormattedPaymentMethod.vue
index d8e9d68a..cd452c48 100644
--- a/packages/ui/src/components/billing/FormattedPaymentMethod.vue
+++ b/packages/ui/src/components/billing/FormattedPaymentMethod.vue
@@ -1,9 +1,8 @@
-
-
-
-
- {{ label }}
-
-
-
-
diff --git a/packages/ui/src/components/nav/NavRow.vue b/packages/ui/src/components/nav/NavRow.vue
deleted file mode 100644
index 45d64594..00000000
--- a/packages/ui/src/components/nav/NavRow.vue
+++ /dev/null
@@ -1,166 +0,0 @@
-
-
-
-
-
-
-
diff --git a/packages/ui/src/components/nav/NavStack.vue b/packages/ui/src/components/nav/NavStack.vue
deleted file mode 100644
index 73cb4fdf..00000000
--- a/packages/ui/src/components/nav/NavStack.vue
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
+
diff --git a/apps/frontend/src/components/ui/servers/BackupRenameModal.vue b/packages/ui/src/components/servers/backups/BackupRenameModal.vue
similarity index 54%
rename from apps/frontend/src/components/ui/servers/BackupRenameModal.vue
rename to packages/ui/src/components/servers/backups/BackupRenameModal.vue
index 4fe1f614..d19d7e96 100644
--- a/apps/frontend/src/components/ui/servers/BackupRenameModal.vue
+++ b/packages/ui/src/components/servers/backups/BackupRenameModal.vue
@@ -23,8 +23,8 @@
-
-
+
+
Renaming...
@@ -45,41 +45,61 @@
diff --git a/packages/ui/src/components/servers/backups/index.ts b/packages/ui/src/components/servers/backups/index.ts
new file mode 100644
index 00000000..f63786be
--- /dev/null
+++ b/packages/ui/src/components/servers/backups/index.ts
@@ -0,0 +1,6 @@
+export { default as BackupCreateModal } from './BackupCreateModal.vue'
+export { default as BackupDeleteModal } from './BackupDeleteModal.vue'
+export { default as BackupItem } from './BackupItem.vue'
+export { default as BackupRenameModal } from './BackupRenameModal.vue'
+export { default as BackupRestoreModal } from './BackupRestoreModal.vue'
+export { default as BackupWarning } from './BackupWarning.vue'
diff --git a/packages/ui/src/components/servers/icons/index.ts b/packages/ui/src/components/servers/icons/index.ts
new file mode 100644
index 00000000..71498d85
--- /dev/null
+++ b/packages/ui/src/components/servers/icons/index.ts
@@ -0,0 +1,2 @@
+export { default as LoaderIcon } from './LoaderIcon.vue'
+export { default as ServerIcon } from './ServerIcon.vue'
diff --git a/packages/ui/src/components/servers/index.ts b/packages/ui/src/components/servers/index.ts
new file mode 100644
index 00000000..8a474342
--- /dev/null
+++ b/packages/ui/src/components/servers/index.ts
@@ -0,0 +1,7 @@
+export * from './backups'
+export * from './icons'
+export * from './labels'
+export * from './marketing'
+export type { PendingChange } from './ServerListing.vue'
+export { default as ServerListing } from './ServerListing.vue'
+export { default as ServersPromo } from './ServersPromo.vue'
diff --git a/packages/ui/src/components/servers/labels/ServerGameLabel.vue b/packages/ui/src/components/servers/labels/ServerGameLabel.vue
index 9c2a3922..6bbbbdec 100644
--- a/packages/ui/src/components/servers/labels/ServerGameLabel.vue
+++ b/packages/ui/src/components/servers/labels/ServerGameLabel.vue
@@ -7,7 +7,7 @@
diff --git a/packages/ui/src/components/servers/labels/ServerLoaderLabel.vue b/packages/ui/src/components/servers/labels/ServerLoaderLabel.vue
index aeff6fd0..8b7de765 100644
--- a/packages/ui/src/components/servers/labels/ServerLoaderLabel.vue
+++ b/packages/ui/src/components/servers/labels/ServerLoaderLabel.vue
@@ -6,7 +6,7 @@
diff --git a/packages/ui/src/components/servers/labels/index.ts b/packages/ui/src/components/servers/labels/index.ts
new file mode 100644
index 00000000..abd2e5cd
--- /dev/null
+++ b/packages/ui/src/components/servers/labels/index.ts
@@ -0,0 +1 @@
+export { default as ServerInfoLabels } from './ServerInfoLabels.vue'
diff --git a/packages/ui/src/components/servers/marketing/MedalServerListing.vue b/packages/ui/src/components/servers/marketing/MedalServerListing.vue
index 297676b6..2648ed21 100644
--- a/packages/ui/src/components/servers/marketing/MedalServerListing.vue
+++ b/packages/ui/src/components/servers/marketing/MedalServerListing.vue
@@ -8,7 +8,7 @@
>
@@ -128,7 +128,8 @@
+
+
diff --git a/packages/ui/src/pages/servers/manage/index.vue b/packages/ui/src/pages/hosting/manage/index.vue
similarity index 97%
rename from packages/ui/src/pages/servers/manage/index.vue
rename to packages/ui/src/pages/hosting/manage/index.vue
index 41548075..7fb314c8 100644
--- a/packages/ui/src/pages/servers/manage/index.vue
+++ b/packages/ui/src/pages/hosting/manage/index.vue
@@ -30,8 +30,8 @@
on getting them back online.
- If you recently purchased your Modrinth Server, it is currently in a queue and will
- appear here as soon as it's ready.
+ If you recently purchased your Modrinth Hosting server, it is currently in a queue and
+ will appear here as soon as it's ready. Do not attempt to purchase a new server.
@@ -96,9 +96,9 @@
"
/>
You don't have any servers yet!
-
Modrinth Servers is a new way to play modded Minecraft with your friends.
+
Modrinth Hosting is a new way to play modded Minecraft with your friends.
- Create a Server
+ Create a server
diff --git a/packages/ui/src/pages/index.ts b/packages/ui/src/pages/index.ts
index c7c34bf8..cebba2a4 100644
--- a/packages/ui/src/pages/index.ts
+++ b/packages/ui/src/pages/index.ts
@@ -1 +1,2 @@
-export { default as ServersManagePageIndex } from './servers/manage/index.vue'
+export { default as ServersManageBackupsPage } from './hosting/manage/backups.vue'
+export { default as ServersManagePageIndex } from './hosting/manage/index.vue'
diff --git a/packages/ui/src/providers/index.ts b/packages/ui/src/providers/index.ts
index 214a0f05..d4a06d62 100644
--- a/packages/ui/src/providers/index.ts
+++ b/packages/ui/src/providers/index.ts
@@ -79,5 +79,7 @@ export function createContext(
}
export * from './api-client'
+export * from './page-context'
export * from './project-page'
+export * from './server-context'
export * from './web-notifications'
diff --git a/packages/ui/src/providers/page-context.ts b/packages/ui/src/providers/page-context.ts
new file mode 100644
index 00000000..9b77cd8b
--- /dev/null
+++ b/packages/ui/src/providers/page-context.ts
@@ -0,0 +1,14 @@
+import type { Ref } from 'vue'
+
+import { createContext } from '.'
+
+export interface PageContext {
+ // pages may render sidebar content in #sidebar-teleport-target instead of in the main layout when true
+ hierarchicalSidebarAvailable: Ref
+ showAds: Ref
+}
+
+export const [injectPageContext, providePageContext] = createContext(
+ 'root',
+ 'pageContext',
+)
diff --git a/packages/ui/src/providers/project-page.ts b/packages/ui/src/providers/project-page.ts
index cc9cbed4..d1b71d1e 100644
--- a/packages/ui/src/providers/project-page.ts
+++ b/packages/ui/src/providers/project-page.ts
@@ -9,6 +9,7 @@ export interface ProjectPageContext {
projectV2: Ref
projectV3: Ref
refreshProject: () => Promise
+ refreshVersions: () => Promise
currentMember: Ref
}
diff --git a/packages/ui/src/providers/server-context.ts b/packages/ui/src/providers/server-context.ts
new file mode 100644
index 00000000..08f3b2c6
--- /dev/null
+++ b/packages/ui/src/providers/server-context.ts
@@ -0,0 +1,32 @@
+import type { Archon } from '@modrinth/api-client'
+import type { ComputedRef, Reactive, Ref } from 'vue'
+
+import { createContext } from '.'
+
+export type BackupTaskState = {
+ progress: number
+ state: Archon.Backups.v1.BackupState
+}
+
+export type BackupProgressEntry = {
+ file?: BackupTaskState
+ create?: BackupTaskState
+ restore?: BackupTaskState
+}
+
+export type BackupsState = Map
+
+export interface ModrinthServerContext {
+ readonly serverId: string
+ readonly server: Ref
+
+ // Websocket state
+ readonly isConnected: Ref
+ readonly powerState: Ref
+ readonly isServerRunning: ComputedRef
+ readonly backupsState: Reactive
+ markBackupCancelled: (backupId: string) => void
+}
+
+export const [injectModrinthServerContext, provideModrinthServerContext] =
+ createContext('[id].vue', 'modrinthServerContext')
diff --git a/packages/ui/src/providers/user-page.ts b/packages/ui/src/providers/user-page.ts
new file mode 100644
index 00000000..4c41d1e1
--- /dev/null
+++ b/packages/ui/src/providers/user-page.ts
@@ -0,0 +1,13 @@
+import type { Labrinth } from '@modrinth/api-client'
+import type { Ref } from 'vue'
+
+import { createContext } from '.'
+
+export interface UserPageContext {
+ user: Ref
+}
+
+export const [injectUserPageContext, provideUserPageContext] = createContext(
+ 'root',
+ 'userPageContext',
+)
diff --git a/packages/ui/src/styles/tailwind-utilities.css b/packages/ui/src/styles/tailwind-utilities.css
new file mode 100644
index 00000000..adc17e4f
--- /dev/null
+++ b/packages/ui/src/styles/tailwind-utilities.css
@@ -0,0 +1,14 @@
+@layer utilities {
+ .heading-xl {
+ @apply m-0 text-xl font-semibold leading-none text-contrast;
+ }
+ .heading-2xl {
+ @apply m-0 text-2xl font-semibold leading-none text-contrast;
+ }
+ .heading-3xl {
+ @apply m-0 text-3xl font-semibold leading-none text-contrast;
+ }
+ .heading-4xl {
+ @apply m-0 text-4xl font-semibold leading-none text-contrast;
+ }
+}
diff --git a/packages/ui/src/utils/auto-icons.ts b/packages/ui/src/utils/auto-icons.ts
new file mode 100644
index 00000000..cdcb76df
--- /dev/null
+++ b/packages/ui/src/utils/auto-icons.ts
@@ -0,0 +1,208 @@
+import {
+ ArchiveIcon,
+ BoxIcon,
+ BracesIcon,
+ CalendarIcon,
+ CardIcon,
+ CurrencyIcon,
+ FileArchiveIcon,
+ FileCodeIcon,
+ FileIcon,
+ FileImageIcon,
+ FileTextIcon,
+ FolderOpenIcon,
+ GithubIcon,
+ GlassesIcon,
+ GlobeIcon,
+ InfoIcon,
+ IssuesIcon,
+ LinkIcon,
+ LockIcon,
+ PackageOpenIcon,
+ PaintbrushIcon,
+ PayPalIcon,
+ PlugIcon,
+ PolygonIcon,
+ UnknownIcon,
+ UpdatedIcon,
+ USDCColorIcon,
+ XCircleIcon,
+ XIcon,
+} from '@modrinth/assets'
+import type { ProjectStatus, ProjectType } from '@modrinth/utils'
+import type { Component } from 'vue'
+
+export const PROJECT_TYPE_ICONS: Record = {
+ mod: BoxIcon,
+ modpack: PackageOpenIcon,
+ resourcepack: PaintbrushIcon,
+ shader: GlassesIcon,
+ plugin: PlugIcon,
+ datapack: BracesIcon,
+ project: BoxIcon,
+}
+
+export const PAYMENT_METHOD_ICONS: Record = {
+ card: CardIcon,
+ cashapp: CurrencyIcon,
+ paypal: PayPalIcon,
+}
+
+export const SOCIAL_PLATFORM_ICONS: Record = {
+ discord: GithubIcon,
+ github: GithubIcon,
+}
+
+export const SEVERITY_ICONS: Record = {
+ info: InfoIcon,
+ warning: IssuesIcon,
+ error: XCircleIcon,
+ critical: XCircleIcon,
+}
+
+export const PROJECT_STATUS_ICONS: Record = {
+ approved: GlobeIcon,
+ unlisted: LinkIcon,
+ withheld: LinkIcon,
+ private: LockIcon,
+ scheduled: CalendarIcon,
+ draft: FileTextIcon,
+ archived: ArchiveIcon,
+ rejected: XIcon,
+ processing: UpdatedIcon,
+ unknown: UnknownIcon,
+}
+
+export const DIRECTORY_ICONS: Record = {
+ config: FolderOpenIcon,
+ world: FolderOpenIcon,
+ resourcepacks: PaintbrushIcon,
+ _default: FolderOpenIcon,
+}
+
+const CURRENCY_CONFIG: Record = {
+ usdc: { icon: USDCColorIcon, color: 'text-blue' },
+}
+
+const BLOCKCHAIN_CONFIG: Record = {
+ polygon: { icon: PolygonIcon, color: 'text-purple' },
+}
+
+export const CODE_EXTENSIONS: readonly string[] = [
+ 'json',
+ 'json5',
+ 'jsonc',
+ 'java',
+ 'kt',
+ 'kts',
+ 'sh',
+ 'bat',
+ 'ps1',
+ 'yml',
+ 'yaml',
+ 'toml',
+ 'js',
+ 'ts',
+ 'py',
+ 'rb',
+ 'php',
+ 'html',
+ 'css',
+ 'cpp',
+ 'c',
+ 'h',
+ 'rs',
+ 'go',
+] as const
+
+export const TEXT_EXTENSIONS: readonly string[] = [
+ 'txt',
+ 'md',
+ 'log',
+ 'cfg',
+ 'conf',
+ 'properties',
+ 'ini',
+ 'sk',
+] as const
+export const IMAGE_EXTENSIONS: readonly string[] = [
+ 'png',
+ 'jpg',
+ 'jpeg',
+ 'gif',
+ 'svg',
+ 'webp',
+] as const
+const ARCHIVE_EXTENSIONS: string[] = ['zip', 'jar', 'tar', 'gz', 'rar', '7z'] as const
+
+export function getProjectTypeIcon(projectType: ProjectType): Component {
+ return PROJECT_TYPE_ICONS[projectType] ?? BoxIcon
+}
+
+export function getPaymentMethodIcon(method: string): Component {
+ return PAYMENT_METHOD_ICONS[method] ?? UnknownIcon
+}
+
+export function getSocialPlatformIcon(platform: string): Component {
+ return SOCIAL_PLATFORM_ICONS[platform.toLowerCase()] ?? UnknownIcon
+}
+
+export function getSeverityIcon(severity: string): Component {
+ return SEVERITY_ICONS[severity] ?? InfoIcon
+}
+
+export function getProjectStatusIcon(status: ProjectStatus): Component {
+ return PROJECT_STATUS_ICONS[status] ?? UnknownIcon
+}
+
+export function getDirectoryIcon(name: string): Component {
+ return DIRECTORY_ICONS[name.toLowerCase()] ?? DIRECTORY_ICONS._default
+}
+
+export function getFileExtensionIcon(extension: string): Component {
+ const ext: string = extension.toLowerCase()
+
+ if (CODE_EXTENSIONS.includes(ext)) {
+ return FileCodeIcon
+ }
+ if (TEXT_EXTENSIONS.includes(ext)) {
+ return FileTextIcon
+ }
+ if (IMAGE_EXTENSIONS.includes(ext)) {
+ return FileImageIcon
+ }
+ if (ARCHIVE_EXTENSIONS.includes(ext)) {
+ return FileArchiveIcon
+ }
+
+ return FileIcon
+}
+
+export function getFileIcon(fileName: string): Component {
+ const extension = fileName.split('.').pop()?.toLowerCase() || ''
+ return getFileExtensionIcon(extension)
+}
+
+export function getCurrencyIcon(currency: string): Component | null {
+ const lower = currency.toLowerCase()
+ const key = Object.keys(CURRENCY_CONFIG).find((k) => lower.includes(k))
+ return key ? CURRENCY_CONFIG[key].icon : null
+}
+
+export function getCurrencyColor(currency: string): string {
+ const lower = currency.toLowerCase()
+ const key = Object.keys(CURRENCY_CONFIG).find((k) => lower.includes(k))
+ return key ? CURRENCY_CONFIG[key].color : 'text-contrast'
+}
+
+export function getBlockchainIcon(blockchain: string): Component | null {
+ const lower = blockchain.toLowerCase()
+ const key = Object.keys(BLOCKCHAIN_CONFIG).find((k) => lower.includes(k))
+ return key ? BLOCKCHAIN_CONFIG[key].icon : null
+}
+
+export function getBlockchainColor(blockchain: string): string {
+ const lower = blockchain.toLowerCase()
+ const key = Object.keys(BLOCKCHAIN_CONFIG).find((k) => lower.includes(k))
+ return key ? BLOCKCHAIN_CONFIG[key].color : 'text-contrast'
+}
diff --git a/packages/ui/src/utils/common-messages.ts b/packages/ui/src/utils/common-messages.ts
index 30d04163..b592ce4c 100644
--- a/packages/ui/src/utils/common-messages.ts
+++ b/packages/ui/src/utils/common-messages.ts
@@ -433,6 +433,14 @@ export const commonProjectTypeCategoryMessages = defineMessages({
id: 'project-type.shader.category',
defaultMessage: 'Shaders',
},
+ server: {
+ id: 'project-type.server.category',
+ defaultMessage: 'Servers',
+ },
+ project: {
+ id: 'project-type.project.category',
+ defaultMessage: 'Projects',
+ },
})
export const commonProjectTypeTitleMessages = defineMessages({
@@ -460,6 +468,14 @@ export const commonProjectTypeTitleMessages = defineMessages({
id: 'project-type.shader.capital',
defaultMessage: '{count, plural, one {Shader} other {Shaders}}',
},
+ server: {
+ id: 'project-type.server.capital',
+ defaultMessage: '{count, plural, one {Server} other {Servers}}',
+ },
+ project: {
+ id: 'project-type.project.lowercase',
+ defaultMessage: '{count, plural, one {Project} other {Projects}}',
+ },
})
export const commonProjectTypeSentenceMessages = defineMessages({
@@ -487,6 +503,14 @@ export const commonProjectTypeSentenceMessages = defineMessages({
id: 'project-type.shader.lowercase',
defaultMessage: '{count, plural, one {shader} other {shaders}}',
},
+ server: {
+ id: 'project-type.server.lowercase',
+ defaultMessage: '{count, plural, one {server} other {servers}}',
+ },
+ project: {
+ id: 'project-type.project.lowercase',
+ defaultMessage: '{count, plural, one {project} other {projects}}',
+ },
})
export const commonSettingsMessages = defineMessages({
diff --git a/packages/ui/src/utils/index.ts b/packages/ui/src/utils/index.ts
index 2a66710e..6d77f1b6 100644
--- a/packages/ui/src/utils/index.ts
+++ b/packages/ui/src/utils/index.ts
@@ -1,5 +1,7 @@
+export * from './auto-icons'
export * from './common-messages'
export * from './game-modes'
export * from './notices'
export * from './savable'
export * from './search'
+export * from './vue-children'
diff --git a/packages/ui/src/utils/search.ts b/packages/ui/src/utils/search.ts
index af186e4c..e06c4076 100644
--- a/packages/ui/src/utils/search.ts
+++ b/packages/ui/src/utils/search.ts
@@ -1,3 +1,4 @@
+import type { Labrinth } from '@modrinth/api-client'
import { ClientIcon, ServerIcon } from '@modrinth/assets'
import { formatCategory, formatCategoryHeader, sortByNameOrNumber } from '@modrinth/utils'
import { defineMessage, useVIntl } from '@vintl/vintl'
@@ -67,25 +68,10 @@ const ALL_PROJECT_TYPES: ProjectType[] = [
'plugin',
]
-export interface Platform {
- name: string
- icon: string
- supported_project_types: ProjectType[]
- default: boolean
- formatted_name: string
-}
-
-export interface Category {
- icon: string
- name: string
- project_type: ProjectType
- header: string
-}
-
export interface Tags {
- gameVersions: GameVersion[]
- loaders: Platform[]
- categories: Category[]
+ gameVersions: Labrinth.Tags.v2.GameVersion[]
+ loaders: Labrinth.Tags.v2.Loader[]
+ categories: Labrinth.Tags.v2.Category[]
}
export interface SortType {
diff --git a/apps/frontend/src/utils/vue-children.ts b/packages/ui/src/utils/vue-children.ts
similarity index 86%
rename from apps/frontend/src/utils/vue-children.ts
rename to packages/ui/src/utils/vue-children.ts
index 419a77a5..ff12ee20 100644
--- a/apps/frontend/src/utils/vue-children.ts
+++ b/packages/ui/src/utils/vue-children.ts
@@ -8,7 +8,7 @@ import { createTextVNode, isVNode, toDisplayString, type VNode } from 'vue'
* @returns Either the original VNode or a text VNode containing child converted
* to a display string.
*/
-function normalizeChild(child: any): VNode {
+function normalizeChild(child: unknown): VNode {
return isVNode(child) ? child : createTextVNode(toDisplayString(child))
}
@@ -20,6 +20,6 @@ function normalizeChild(child: any): VNode {
* @param children Children to normalize.
* @returns Children with all of non-VNodes converted to display strings.
*/
-export function normalizeChildren(children: any | any[]): VNode[] {
+export function normalizeChildren(children: unknown | unknown[]): VNode[] {
return Array.isArray(children) ? children.map(normalizeChild) : [normalizeChild(children)]
}
diff --git a/packages/ui/tailwind-preset.js b/packages/ui/tailwind-preset.js
new file mode 100644
index 00000000..f73ed47d
--- /dev/null
+++ b/packages/ui/tailwind-preset.js
@@ -0,0 +1,267 @@
+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}',
+ ],
+ 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,
+ },
+}
diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js
new file mode 100644
index 00000000..1160b182
--- /dev/null
+++ b/packages/ui/tailwind.config.js
@@ -0,0 +1,5 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: ['./tailwind-preset.js'],
+ presets: [require('./tailwind-preset.js')],
+}
diff --git a/packages/utils/changelog.ts b/packages/utils/changelog.ts
index 0ec2301f..74eb943f 100644
--- a/packages/utils/changelog.ts
+++ b/packages/utils/changelog.ts
@@ -1,6 +1,6 @@
import dayjs from 'dayjs'
-export type Product = 'web' | 'servers' | 'api' | 'app'
+export type Product = 'web' | 'hosting' | 'api' | 'app'
export type VersionEntry = {
date: dayjs.Dayjs
@@ -10,6 +10,116 @@ export type VersionEntry = {
}
const VERSIONS: VersionEntry[] = [
+ {
+ date: `2025-12-19T13:45:00-08:00`,
+ product: 'web',
+ body: `## Improvements
+- Improved the version creation and editing from feedback we have received:
+ - Made it easier to edit exactly what you want to about a version.
+ - Restored the ability to create and edit versions and gallery images from the public pages.
+ - Changelog stage is now larger.
+ - Fixed modpack uploading.
+ - Fixed version subtitle being limited to 32 characters.
+ - Fixed version links after editing.
+ - Fixed dependency search only showing mod projects.`,
+ },
+ {
+ date: `2025-12-18T13:40:00-08:00`,
+ product: 'web',
+ body: `## Improvements
+- Fixed non-members being informed of version and gallery editing having moved.
+- Fixed being able to de-select the project type in version settings, and then getting stuck.
+- Fixed some issues with non-USD gift card withdrawal.`,
+ },
+ {
+ date: `2025-12-18T12:30:00-08:00`,
+ product: 'web',
+ body: `## Improvements
+- [Overhauled version creation](/news/article/streamlined-version-creation) to be more intelligent and easier to use.
+- Versions and gallery images are now created and edited in project settings.`,
+ },
+ {
+ date: `2025-12-18T11:20:00-08:00`,
+ product: 'web',
+ body: `## Improvements
+- Added support for non-USD gift cards.
+- Fixed issue with gift cards with lots of denominations.
+- Fixed issue with subregions for crypto & bank withdrawals.`,
+ },
+ {
+ date: `2025-12-16T13:15:00-08:00`,
+ product: 'web',
+ body: `## Improvements
+- Fixed collection pages requiring auth.`,
+ },
+ {
+ date: `2025-12-16T13:15:00-08:00`,
+ product: 'app',
+ version: '0.10.23',
+ body: `## Improvements
+- Fixed installation of newer NeoForge versions.
+- Added Java 25 support to settings for Minecraft 26.1.`,
+ },
+ {
+ date: `2025-12-11T17:00:00-08:00`,
+ product: 'app',
+ version: '0.10.22',
+ body: `## Improvements
+- Updated Modrinth Servers branding to new Modrinth Hosting branding.
+- Fixed server pinging blocking the app from loading.
+- Fixed instance overrides for window and Java settings not being able to be disabled.`,
+ },
+ {
+ date: `2025-12-11T16:15:00-08:00`,
+ product: 'hosting',
+ body: `## Improvements
+- Fixed some issues with the content list when disabling content.
+- Improved the design of server notices.`,
+ },
+ {
+ date: `2025-12-11T16:15:00-08:00`,
+ product: 'web',
+ body: `## Improvements
+- Moved search pages to /discover/.
+- Updated collections page design to be more modern.
+- Fixed some inconsistencies with collection icons around the site.
+- Fixed some issues with the revenue balance bar.
+- Fixed the width of news articles on the landing page.
+- Made game versions automatically update.`,
+ },
+ {
+ date: `2025-12-08T10:30:00-08:00`,
+ product: 'web',
+ body: `## Improvements
+- Fixed license URL being unable to remove from projects.`,
+ },
+ {
+ date: `2025-12-05T12:00:00-08:00`,
+ product: 'hosting',
+ body: `## Improvements
+- Implemented some feedback from the new backups page.
+- Improved node error handling.`,
+ },
+ {
+ date: `2025-12-03T18:40:00-08:00`,
+ product: 'hosting',
+ body: `## Improvements
+- Overhauled the backups page to be clearer and significantly more reliable.`,
+ },
+ {
+ date: `2025-12-03T14:45:00-08:00`,
+ product: 'web',
+ body: `## Changes
+- Updated Modrinth Servers branding to new Modrinth Hosting branding.`,
+ },
+ {
+ date: `2025-11-28T11:45:00-08:00`,
+ product: 'app',
+ version: '0.10.21',
+ body: `## Improvements
+- Install dependencies added in an update automatically.
+- Fixed auth server check.`,
+ },
{
date: `2025-11-19T15:15:00-08:00`,
product: 'app',
@@ -22,7 +132,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-11-14T12:15:00-08:00`,
- product: 'servers',
+ product: 'hosting',
body: `## Improvements
- Improved the performance of the servers list.
- Fixed startup commands not being updated properly.
@@ -164,7 +274,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-10-14T18:45:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Removed 'Prepare download' step for downloading backups, you can now just download them directly.`,
},
@@ -250,7 +360,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-09-08T14:45:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed world seed being rounded in options.`,
},
@@ -330,7 +440,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-08-28T16:50:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed issue with Files page not showing files in the correct order sometimes.
- Fixed Medal servers showing a confusing cancellation/suspension notice.`,
@@ -352,7 +462,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-08-19T11:10:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Improved upgrading experience.`,
},
@@ -365,7 +475,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-08-18T09:10:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed various dropdowns not appearing.`,
},
@@ -448,7 +558,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-08-01T21:30:00-04:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Server status information is now correctly displayed in the 'My Servers' page. ([#4071](https://github.com/modrinth/code/pull/4071))
- Fixed an error with displaying startup settings.
@@ -502,19 +612,19 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-07-08T11:10:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Reapplied error handling improvements, with more improvements.`,
},
{
date: `2025-07-07T22:20:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed issue with Servers panel failing to load.`,
},
{
date: `2025-07-07T17:45:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Reverted error handling improvements.`,
},
@@ -596,7 +706,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-06-30T19:15:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Progress will now show when installing Modrinth Pack (.mrpack) files.
- Fixed storage stats not linking to Files page.
@@ -611,7 +721,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-06-26T11:00:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed support bubble overlapping notifications sometimes.
- Fixed race condition when creating backups.`,
@@ -635,21 +745,21 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-06-16T11:00:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Improved error handling.
- Rolled out hotfixes with the previous days' updates.'`,
},
{
date: `2025-06-15T16:25:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed installing modpacks from search.
- Fixed setting subdomains.`,
},
{
date: `2025-06-15T14:30:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed various issues with the panel loading improperly in certain cases.
- Fixed CPU icon being smaller than the rest.
@@ -666,7 +776,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-06-03T14:35:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Added support for servers in Europe.
- Added server setup for new servers upon opening the panel for the first time.`,
@@ -680,7 +790,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-05-08T09:00:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Added the ability to extract .zip files in the Files page.
- Added the ability to extract a remote .zip file from a URL, or from a CurseForge modpack version URL.
@@ -735,7 +845,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-04-28T19:45:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Added support for installing snapshot versions of Minecraft.
@@ -788,7 +898,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-04-17T02:25:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Completely overhauled the Backups interface and fixed them being non-functional.
- Backups will now show progress when creating and restoring.
@@ -800,7 +910,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-04-15T16:35:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Added ability to send surveys to customers in the panel via notices.
@@ -809,7 +919,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-04-12T22:10:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Added ability to notify customers in the panel with notices concerning their servers.`,
},
@@ -853,7 +963,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-03-24T22:30:00-07:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed server plugin loaders not being populated when browsing for plugins
- Fixed modpack search being filtered by Minecraft version when browsing for modpacks.`,
@@ -911,7 +1021,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-02-25T10:20:00-08:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Fixed server upgrades being allowed when out of stock, despite warning.`,
},
@@ -952,7 +1062,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-02-18T14:30:00-08:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Links will now be detected in console line viewer modal.
@@ -972,7 +1082,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-02-16T19:10:00-08:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Improvements
- Check for availability before allowing a server upgrade.`,
},
@@ -984,7 +1094,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-02-12T19:10:00-08:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Added server upgrades to switch to a larger plan as an option in billing settings.`,
},
@@ -1021,7 +1131,7 @@ const VERSIONS: VersionEntry[] = [
},
{
date: `2025-02-10T08:00:00-08:00`,
- product: 'servers',
+ product: 'hosting',
version: `February Release`,
body: `### Added
- You can now search and filter through your server's console in the Overview tab, jump to specific results to see the log in context, select them, and copy them.
@@ -1072,7 +1182,7 @@ Contributed by [IMB11](https://github.com/modrinth/code/pull/1301).`,
},
{
date: `2025-01-10T09:00:00-08:00`,
- product: 'servers',
+ product: 'hosting',
version: 'January Release',
body: `### Added
- Added drag & drop upload support for mod and plugin files on the content page.
@@ -1102,7 +1212,7 @@ Contributed by [IMB11](https://github.com/modrinth/code/pull/1301).`,
},
{
date: `2024-12-26T22:05:00-08:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Added ability for users to clean install modpacks when switching versions.
@@ -1151,7 +1261,7 @@ Contributed by [IMB11](https://github.com/modrinth/code/pull/1301).`,
},
{
date: `2024-12-21T16:00:00-08:00`,
- product: 'servers',
+ product: 'hosting',
body: `### Added
- Drag and drop anything in the file manager.
- Added file upload queue status bar.
@@ -1172,7 +1282,7 @@ Contributed by [IMB11](https://github.com/modrinth/code/pull/1301).`,
},
{
date: `2024-12-11T22:18:45-08:00`,
- product: 'servers',
+ product: 'hosting',
version: `December Release`,
body: `### Added
- Expanded loader support to include **Paper** and **Purpur** servers, offering fully native plugin compatibility.
diff --git a/packages/utils/highlightjs/index.ts b/packages/utils/highlightjs/index.ts
index c8e7ba5c..dbfbae08 100644
--- a/packages/utils/highlightjs/index.ts
+++ b/packages/utils/highlightjs/index.ts
@@ -55,6 +55,8 @@ hljs.registerAliases(['toml'], { languageName: 'ini' })
hljs.registerAliases(['yml'], { languageName: 'yaml' })
hljs.registerAliases(['html', 'htm', 'xhtml', 'mcui', 'fxml'], { languageName: 'xml' })
+export { hljs }
+
export const renderHighlightedString = (string) =>
configuredXss.process(
md({
@@ -71,3 +73,34 @@ export const renderHighlightedString = (string) =>
},
}).render(string),
)
+
+export const highlightCodeLines = (code: string, language: string): string[] => {
+ if (!code) return []
+
+ if (!hljs.getLanguage(language)) {
+ return code.split('\n')
+ }
+
+ try {
+ const highlighted = hljs.highlight(code, { language }).value
+ const openTags: string[] = []
+
+ const processedHtml = highlighted.replace(/(]+>)|(<\/span>)|(\n)/g, (match) => {
+ if (match === '\n') {
+ return ''.repeat(openTags.length) + '\n' + openTags.join('')
+ }
+
+ if (match === '') {
+ openTags.pop()
+ } else {
+ openTags.push(match)
+ }
+
+ return match
+ })
+
+ return processedHtml.split('\n')
+ } catch {
+ return code.split('\n')
+ }
+}
diff --git a/packages/utils/types.ts b/packages/utils/types.ts
index b3ba0edb..03dbd100 100644
--- a/packages/utils/types.ts
+++ b/packages/utils/types.ts
@@ -283,7 +283,14 @@ export interface FileDependency {
export type Dependency = VersionDependency | ProjectDependency | FileDependency
export type VersionChannel = 'release' | 'beta' | 'alpha'
export type VersionStatus = 'listed' | 'archived' | 'draft' | 'unlisted' | 'scheduled' | 'unknown'
-export type FileType = 'required-resource-pack' | 'optional-resource-pack'
+export type FileType =
+ | 'required-resource-pack'
+ | 'optional-resource-pack'
+ | 'sources-jar'
+ | 'dev-jar'
+ | 'javadoc-jar'
+ | 'signature'
+ | 'unknown'
export interface VersionFileHash {
sha512: string
@@ -291,7 +298,7 @@ export interface VersionFileHash {
}
export interface VersionFile {
- hashes: VersionFileHash[]
+ hashes: VersionFileHash
url: string
filename: string
primary: boolean
diff --git a/packages/utils/utils.ts b/packages/utils/utils.ts
index e820c96e..b536d89b 100644
--- a/packages/utils/utils.ts
+++ b/packages/utils/utils.ts
@@ -134,11 +134,29 @@ export const formatWallet = (name) => {
return capitalizeString(name)
}
-export const formatProjectType = (name) => {
+export const formatProjectType = (name, short = false) => {
+ if (short) {
+ if (name === 'resourcepack') {
+ return 'RPK'
+ } else if (name === 'mod') {
+ return 'MOD'
+ } else if (name === 'modpack') {
+ return 'MPK'
+ } else if (name === 'shader') {
+ return 'SHD'
+ } else if (name === 'plugin') {
+ return 'PLG'
+ } else if (name === 'datapack') {
+ return 'DPK'
+ }
+ }
+
if (name === 'resourcepack') {
return 'Resource Pack'
} else if (name === 'datapack') {
return 'Data Pack'
+ } else if (name === 'modpack') {
+ return 'Modpack'
}
return capitalizeString(name)
@@ -287,7 +305,7 @@ export const formatVersions = (versionArray, gameVersions) => {
return (output.length === 0 ? versionArray : output).join(', ')
}
-export function cycleValue(value, values) {
+export function cycleValue(value: T, values: T[]): T {
const index = values.indexOf(value) + 1
return values[index % values.length]
}
@@ -305,21 +323,23 @@ export const fileIsValid = (file, validationOptions) => {
}
export const acceptFileFromProjectType = (projectType) => {
+ const commonTypes = '.sig,.asc,.gpg,application/pgp-signature,application/pgp-keys'
switch (projectType) {
case 'mod':
- return '.jar,.zip,.litemod,application/java-archive,application/x-java-archive,application/zip'
+ return `.jar,.zip,.litemod,application/java-archive,application/x-java-archive,application/zip,${commonTypes}`
case 'plugin':
- return '.jar,.zip,application/java-archive,application/x-java-archive,application/zip'
+ return `.jar,.zip,application/java-archive,application/x-java-archive,application/zip,${commonTypes}`
case 'resourcepack':
- return '.zip,application/zip'
+ return `.zip,application/zip,${commonTypes}`
case 'shader':
- return '.zip,application/zip'
+ return `.zip,application/zip,${commonTypes}`
case 'datapack':
- return '.zip,application/zip'
+ return `.zip,application/zip,${commonTypes}`
case 'modpack':
- return '.mrpack,application/x-modrinth-modpack+zip,application/zip'
+ return `.mrpack,application/x-modrinth-modpack+zip,application/zip,${commonTypes}`
default:
- return '*'
+ // all of the above
+ return `.jar,.zip,.litemod,.mrpack,application/java-archive,application/x-java-archive,application/zip,application/x-modrinth-modpack+zip,${commonTypes}`
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1afc7164..dd8e47df 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -177,6 +177,9 @@ importers:
vite:
specifier: ^5.4.6
version: 5.4.19(@types/node@20.19.9)(sass@1.90.0)(terser@5.43.1)
+ vue-component-type-helpers:
+ specifier: ^3.1.8
+ version: 3.2.1
vue-tsc:
specifier: ^2.1.6
version: 2.2.12(typescript@5.9.2)
@@ -287,6 +290,9 @@ importers:
highlight.js:
specifier: ^11.7.0
version: 11.11.1
+ iso-3166-2:
+ specifier: 1.0.0
+ version: 1.0.0
js-yaml:
specifier: ^4.1.0
version: 4.1.0
@@ -345,6 +351,9 @@ importers:
'@types/dompurify':
specifier: ^3.0.5
version: 3.2.0
+ '@types/iso-3166-2':
+ specifier: ^1.0.4
+ version: 1.0.4
'@types/node':
specifier: ^20.1.0
version: 20.19.9
@@ -387,6 +396,9 @@ importers:
vite-svg-loader:
specifier: ^5.1.0
version: 5.1.0(vue@3.5.18(typescript@5.9.2))
+ vue-component-type-helpers:
+ specifier: ^3.1.8
+ version: 3.2.1
vue-tsc:
specifier: ^2.0.24
version: 2.2.12(typescript@5.9.2)
@@ -398,6 +410,9 @@ importers:
'@tauri-apps/plugin-http':
specifier: ^2.0.0
version: 2.5.1
+ mitt:
+ specifier: ^3.0.1
+ version: 3.0.1
ofetch:
specifier: ^1.4.1
version: 1.4.1
@@ -464,6 +479,9 @@ importers:
packages/moderation:
dependencies:
+ '@modrinth/api-client':
+ specifier: workspace:*
+ version: link:../api-client
'@modrinth/assets':
specifier: workspace:*
version: link:../assets
@@ -647,6 +665,9 @@ importers:
vue:
specifier: ^3.5.13
version: 3.5.18(typescript@5.9.2)
+ vue-component-type-helpers:
+ specifier: ^3.1.8
+ version: 3.2.1
vue-router:
specifier: 4.3.0
version: 4.3.0(vue@3.5.18(typescript@5.9.2))
@@ -2901,6 +2922,9 @@ packages:
'@types/html-minifier-terser@7.0.2':
resolution: {integrity: sha512-mm2HqV22l8lFQh4r2oSsOEVea+m0qqxEmwpc9kC1p/XzmjLWrReR9D/GRs8Pex2NX/imyEH9c5IU/7tMBQCHOA==}
+ '@types/iso-3166-2@1.0.4':
+ resolution: {integrity: sha512-tXaeT4FDobC8rAy6LoFvbGA4vhOQQNIdSRC5DAoYfT3D9ohnKHkDFxHzSln6WqTKVeKLrnMiMQubM8m3fqNp/w==}
+
'@types/js-yaml@4.0.9':
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
@@ -5508,6 +5532,9 @@ packages:
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
engines: {node: '>=16'}
+ iso-3166-2@1.0.0:
+ resolution: {integrity: sha512-xLAazfKZzwlsg/Zz/GQGQk3jJez5/2ORrjD3TjSuqz/arMht/xTK49c0GOE3afO/gEd9tHtBVVlfBla01unUng==}
+
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
@@ -8343,6 +8370,9 @@ packages:
vue-bundle-renderer@2.1.2:
resolution: {integrity: sha512-M4WRBO/O/7G9phGaGH9AOwOnYtY9ZpPoDVpBpRzR2jO5rFL9mgIlQIgums2ljCTC2HL1jDXFQc//CzWcAQHgAw==}
+ vue-component-type-helpers@3.2.1:
+ resolution: {integrity: sha512-gKV7XOkQl4urSuLHNY1tnVQf7wVgtb/mKbRyxSLWGZUY9RK7aDPhBenTjm+i8ZFe0zC2PZeHMPtOZXZfyaFOzQ==}
+
vue-confetti-explosion@1.0.2:
resolution: {integrity: sha512-80OboM3/6BItIoZ6DpNcZFqGpF607kjIVc5af56oKgtFmt5yWehvJeoYhkzYlqxrqdBe0Ko4Ie3bWrmLau+dJw==}
engines: {node: '>=12'}
@@ -11001,6 +11031,8 @@ snapshots:
'@types/html-minifier-terser@7.0.2': {}
+ '@types/iso-3166-2@1.0.4': {}
+
'@types/js-yaml@4.0.9': {}
'@types/json-schema@7.0.15': {}
@@ -14191,6 +14223,8 @@ snapshots:
isexe@3.1.1: {}
+ iso-3166-2@1.0.0: {}
+
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
@@ -17632,6 +17666,8 @@ snapshots:
dependencies:
ufo: 1.6.1
+ vue-component-type-helpers@3.2.1: {}
+
vue-confetti-explosion@1.0.2(vue@3.5.18(typescript@5.9.2)):
dependencies:
vue: 3.5.18(typescript@5.9.2)