diff --git a/components/ui/ModerationChecklist.vue b/components/ui/ModerationChecklist.vue
new file mode 100644
index 0000000000..800784f51f
--- /dev/null
+++ b/components/ui/ModerationChecklist.vue
@@ -0,0 +1,1033 @@
+
+
+
Moderation checklist
+
+
You are done moderating this project! There are {{ futureProjects.length }} left.
+
+
+
+ Enter your moderation message here. Remember to check the Moderation tab to answer any
+ questions an author might have!
+
+
+
+
+
+
+
+ Modpack permissions
+
+ ({{ modPackIndex + 1 }} / {{ modPackData.length }})
+
+
+
Loading data...
+
+
All permissions obtained. You may skip this step!
+
+
+
All permission checks complete!
+
+
+
+ Previous
+
+
+
+
+
+
+
+
+ Does this project provide identification and permission for
+ {{ modPackData[modPackIndex].file_name }} ?
+
+
+ Does this project provide attribution for
+ {{ modPackData[modPackIndex].file_name }} ?
+
+
+ Does this project provide proof of permission for
+ {{ modPackData[modPackIndex].file_name }} ?
+
+
+
+ {{ option.name }}
+
+
+
+
+
+
+ Previous
+
+
+
+ Next project
+
+
+
+
+
+
{{ steps[currentStepIndex].question }}
+
+ Rules guidance:
+
+
+
+ Examples of what to reject:
+
+
+
+ Exceptions:
+
+
+
+ Title: {{ project.title }}
+
+
Slug: {{ project.slug }}
+
+ Summary: {{ project.description }}
+
+
+
+ Issues:
+ {{ project.issues_url }}
+
+
+ Source:
+ {{ project.source_url }}
+
+
+ Wiki:
+ {{ project.wiki_url }}
+
+
+ Discord:
+ {{ project.discord_url }}
+
+
+
+ {{ donation.platform }}:
+ {{ donation.url }}
+
+
+
+
+ Categories:
+
+
+
+ Client side: {{ project.client_side }}
+ Server side: {{ project.server_side }}
+
+
+
+ {{ option.name }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/composables/date.js b/composables/date.js
index 69bccfb45e..2641317789 100644
--- a/composables/date.js
+++ b/composables/date.js
@@ -1,7 +1,6 @@
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
-// eslint-disable-next-line import/no-named-as-default-member
dayjs.extend(relativeTime)
export const useCurrentDate = () => useState('currentDate', () => Date.now())
diff --git a/helpers/teams.js b/helpers/teams.js
index 132f59fc36..eedddc16f8 100644
--- a/helpers/teams.js
+++ b/helpers/teams.js
@@ -6,7 +6,7 @@ export const acceptTeamInvite = async (teamId) => {
}
export const removeSelfFromTeam = async (teamId) => {
const auth = await useAuth()
- await removeTeamMember(teamId, auth.user.id)
+ await removeTeamMember(teamId, auth.value.user.id)
}
export const removeTeamMember = async (teamId, userId) => {
await useBaseFetch(`team/${teamId}/members/${userId}`, {
diff --git a/layouts/default.vue b/layouts/default.vue
index 236c58ae62..4f0df3a8aa 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -5,14 +5,16 @@
class="email-nag"
>
- For security purposes, please verify your email address on Modrinth.
- Re-send verification email
+ {{ formatMessage(verifyEmailBannerMessages.title) }}
+
+ {{ formatMessage(verifyEmailBannerMessages.action) }}
+
- For security purposes, please enter your email on Modrinth.
+ {{ formatMessage(addEmailBannerMessages.title) }}
- Visit account settings
+ {{ formatMessage(addEmailBannerMessages.action) }}
@@ -25,12 +27,10 @@
>
- You’re viewing Modrinth’s staging environment
+ {{ formatMessage(stagingBannerMessages.title) }}
- The staging environment is running on a copy of the production Modrinth database. This is
- used for testing and debugging purposes, and may be running in-development versions of the
- Modrinth backend or frontend newer than the production instance.
+ {{ formatMessage(stagingBannerMessages.description) }}
@@ -53,13 +53,13 @@
v-if="auth.user"
to="/dashboard/notifications"
class="control-button button-transparent"
- title="Notifications"
+ :title="formatMessage(commonMessages.notificationsLabel)"
>
@@ -78,7 +78,7 @@
@@ -88,13 +88,15 @@
@{{ auth.user.username }}
-
Visit your profile
+
{{ formatMessage(messages.visitYourProfile) }}
- Create a project
+
+ {{ formatMessage(commonMessages.createAProjectButton) }}
+
@@ -103,15 +105,17 @@
- Notifications
+ {{
+ formatMessage(commonMessages.notificationsLabel)
+ }}
- Dashboard
+ {{ formatMessage(commonMessages.dashboardLabel) }}
- Settings
+ {{ formatMessage(commonMessages.settingsLabel) }}
- Moderation
+ {{ formatMessage(commonMessages.moderationLabel) }}
- Get Modrinth App
+
+ {{ formatMessage(messages.getModrinthApp) }}
+
- Log out
+
+ {{ formatMessage(commonMessages.signOutButton) }}
+
- Sign in
+ {{ formatMessage(commonMessages.signInButton) }}
- Get Modrinth App
+ {{ formatMessage(messages.getModrinthApp) }}
@@ -185,32 +193,32 @@
@{{ auth.user.username }}
-
Visit your profile
+
{{ formatMessage(messages.visitYourProfile) }}
- Sign in
+ {{ formatMessage(commonMessages.signInButton) }}
- Log out
+ {{ formatMessage(commonMessages.signOutButton) }}
- Create a project
+ {{ formatMessage(commonMessages.createAProjectButton) }}
- Collections
+ {{ formatMessage(commonMessages.collectionsLabel) }}
- Moderation
+ {{ formatMessage(commonMessages.moderationLabel) }}
- Settings
+ {{ formatMessage(commonMessages.settingsLabel) }}
- Change theme
+
+ {{ formatMessage(messages.changeTheme) }}
+
-
+
@@ -247,7 +261,7 @@
- Search
+ {{ formatMessage(navMenuMessages.search) }}
@@ -257,7 +271,7 @@
:class="{
'no-active': isMobileMenuOpen || isBrowseMenuOpen,
}"
- title="Notifications"
+ :title="formatMessage(commonMessages.notificationsLabel)"
@click="
() => {
isMobileMenuOpen = false
@@ -267,13 +281,17 @@
>
-
+
@@ -285,7 +303,7 @@
:src="auth.user.avatar_url"
class="user-icon"
:class="{ expanded: isMobileMenuOpen }"
- alt="Your avatar"
+ :alt="formatMessage(messages.yourAvatarAlt)"
aria-hidden="true"
circle
/>
@@ -300,17 +318,24 @@
-
Interact
+
{{ formatMessage(footerMessages.interactTitle) }}
Discord
X (Twitter)
Mastodon
@@ -358,20 +392,20 @@
- Get Modrinth App
+ {{ formatMessage(messages.getModrinthApp) }}
- Change theme
+ {{ formatMessage(messages.changeTheme) }}
- Settings
+ {{ formatMessage(commonMessages.settingsLabel) }}
- NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG.
+ {{ formatMessage(footerMessages.legalDisclaimer) }}
@@ -397,6 +431,8 @@ import ChartIcon from '~/assets/images/utils/chart.svg'
import NavRow from '~/components/ui/NavRow.vue'
import ModalCreation from '~/components/ui/ModalCreation.vue'
import Avatar from '~/components/ui/Avatar.vue'
+import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
+import { commonMessages } from '~/utils/common-messages.ts'
const { formatMessage } = useVIntl()
@@ -408,6 +444,131 @@ const tags = useTags()
const config = useRuntimeConfig()
const route = useRoute()
const link = config.public.siteUrl + route.path.replace(/\/+$/, '')
+
+const verifyEmailBannerMessages = defineMessages({
+ title: {
+ id: 'layout.banner.verify-email.title',
+ defaultMessage: 'For security purposes, please verify your email address on Modrinth.',
+ },
+ action: {
+ id: 'layout.banner.verify-email.action',
+ defaultMessage: 'Re-send verification email',
+ },
+})
+
+const addEmailBannerMessages = defineMessages({
+ title: {
+ id: 'layout.banner.add-email.title',
+ defaultMessage: 'For security purposes, please enter your email on Modrinth.',
+ },
+ action: {
+ id: 'layout.banner.add-email.button',
+ defaultMessage: 'Visit account settings',
+ },
+})
+
+const stagingBannerMessages = defineMessages({
+ title: {
+ id: 'layout.banner.staging.title',
+ defaultMessage: 'You’re viewing Modrinth’s staging environment.',
+ },
+ description: {
+ id: 'layout.banner.staging.description',
+ defaultMessage:
+ 'The staging environment is running on a copy of the production Modrinth database. This is used for testing and debugging purposes, and may be running in-development versions of the Modrinth backend or frontend newer than the production instance.',
+ },
+})
+
+const navMenuMessages = defineMessages({
+ home: {
+ id: 'layout.nav.home',
+ defaultMessage: 'Home',
+ },
+ search: {
+ id: 'layout.nav.search',
+ defaultMessage: 'Search',
+ },
+})
+
+const messages = defineMessages({
+ visitYourProfile: {
+ id: 'layout.label.visit-your-profile',
+ defaultMessage: 'Visit your profile',
+ },
+ toggleMenu: {
+ id: 'layout.menu-toggle.action',
+ defaultMessage: 'Toggle menu',
+ },
+ yourAvatarAlt: {
+ id: 'layout.avatar.alt',
+ defaultMessage: 'Your avatar',
+ },
+ getModrinthApp: {
+ id: 'layout.action.get-modrinth-app',
+ defaultMessage: 'Get Modrinth App',
+ },
+ changeTheme: {
+ id: 'layout.action.change-theme',
+ defaultMessage: 'Change theme',
+ },
+})
+
+const footerMessages = defineMessages({
+ openSource: {
+ id: 'layout.footer.open-source',
+ defaultMessage: 'Modrinth is open source .',
+ },
+ companyTitle: {
+ id: 'layout.footer.company.title',
+ defaultMessage: 'Company',
+ },
+ terms: {
+ id: 'layout.footer.company.terms',
+ defaultMessage: 'Terms',
+ },
+ privacy: {
+ id: 'layout.footer.company.privacy',
+ defaultMessage: 'Privacy',
+ },
+ rules: {
+ id: 'layout.footer.company.rules',
+ defaultMessage: 'Rules',
+ },
+ careers: {
+ id: 'layout.footer.company.careers',
+ defaultMessage: 'Careers',
+ },
+ resourcesTitle: {
+ id: 'layout.footer.resources.title',
+ defaultMessage: 'Resources',
+ },
+ support: {
+ id: 'layout.footer.resources.support',
+ defaultMessage: 'Support',
+ },
+ blog: {
+ id: 'layout.footer.resources.blog',
+ defaultMessage: 'Blog',
+ },
+ docs: {
+ id: 'layout.footer.resources.docs',
+ defaultMessage: 'Docs',
+ },
+ status: {
+ id: 'layout.footer.resources.status',
+ defaultMessage: 'Status',
+ },
+ interactTitle: {
+ id: 'layout.footer.interact.title',
+ defaultMessage: 'Interact',
+ },
+ legalDisclaimer: {
+ id: 'layout.footer.legal-disclaimer',
+ defaultMessage:
+ 'NOT AN OFFICIAL MINECRAFT SERVICE. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.',
+ },
+})
+
useHead({
link: [
{
@@ -416,14 +577,15 @@ useHead({
},
],
})
-
-const description =
- 'Download Minecraft mods, plugins, datapacks, shaders, resourcepacks, and modpacks on Modrinth. ' +
- 'Discover and publish projects on Modrinth with a modern, easy to use interface and API.'
-
useSeoMeta({
title: 'Modrinth',
- description,
+ description: () =>
+ formatMessage({
+ id: 'layout.meta.description',
+ defaultMessage:
+ 'Download Minecraft mods, plugins, datapacks, shaders, resourcepacks, and modpacks on Modrinth. ' +
+ 'Discover and publish projects on Modrinth with a modern, easy to use interface and API.',
+ }),
publisher: 'Modrinth',
themeColor: '#1bd96a',
colorScheme: 'dark light',
@@ -431,7 +593,11 @@ useSeoMeta({
// OpenGraph
ogTitle: 'Modrinth',
ogSiteName: 'Modrinth',
- ogDescription: 'Discover and publish Minecraft content!',
+ ogDescription: () =>
+ formatMessage({
+ id: 'layout.meta.og-description',
+ defaultMessage: 'Discover and publish Minecraft content!',
+ }),
ogType: 'website',
ogImage: 'https://cdn.modrinth.com/modrinth-new.png',
ogUrl: link,
@@ -441,12 +607,67 @@ useSeoMeta({
twitterSite: '@modrinth',
})
-let developerModeCounter = 0
+const developerModeCounter = ref(0)
+
+const isDropdownOpen = ref(false)
+const isMobileMenuOpen = ref(false)
+const isBrowseMenuOpen = ref(false)
+const navRoutes = computed(() => [
+ {
+ label: formatMessage(getProjectTypeMessage('mod', true)),
+ href: '/mods',
+ },
+ {
+ label: formatMessage(getProjectTypeMessage('plugin', true)),
+ href: '/plugins',
+ },
+ {
+ label: formatMessage(getProjectTypeMessage('datapack', true)),
+ href: '/datapacks',
+ },
+ {
+ label: formatMessage(getProjectTypeMessage('shader', true)),
+ href: '/shaders',
+ },
+ {
+ label: formatMessage(getProjectTypeMessage('resourcepack', true)),
+ href: '/resourcepacks',
+ },
+ {
+ label: formatMessage(getProjectTypeMessage('modpack', true)),
+ href: '/modpacks',
+ },
+])
+
+onMounted(() => {
+ if (window && process.client) {
+ window.history.scrollRestoration = 'auto'
+ }
+
+ runAnalytics()
+})
+
+watch(
+ () => route.path,
+ () => {
+ isMobileMenuOpen.value = false
+ isBrowseMenuOpen.value = false
+
+ if (process.client) {
+ document.body.style.overflowY = 'scroll'
+ document.body.setAttribute('tabindex', '-1')
+ document.body.removeAttribute('tabindex')
+ }
+
+ updateCurrentDate()
+ runAnalytics()
+ }
+)
function developerModeIncrement() {
- if (developerModeCounter >= 5) {
+ if (developerModeCounter.value >= 5) {
cosmetics.value.developerMode = !cosmetics.value.developerMode
- developerModeCounter = 0
+ developerModeCounter.value = 0
if (cosmetics.value.developerMode) {
app.$notify({
group: 'main',
@@ -463,7 +684,7 @@ function developerModeIncrement() {
})
}
} else {
- developerModeCounter++
+ developerModeCounter.value++
}
}
@@ -471,110 +692,46 @@ async function logoutUser() {
await logout()
}
+function runAnalytics() {
+ const config = useRuntimeConfig()
+ const replacedUrl = config.public.apiBaseUrl.replace('v2/', '')
+
+ setTimeout(() => {
+ $fetch(`${replacedUrl}analytics/view`, {
+ method: 'POST',
+ body: {
+ url: window.location.href,
+ },
+ headers: {
+ Authorization: auth.value.token,
+ },
+ })
+ .then(() => {})
+ .catch(() => {})
+ })
+}
+function toggleMobileMenu() {
+ isMobileMenuOpen.value = !isMobileMenuOpen.value
+ if (isMobileMenuOpen.value) {
+ isBrowseMenuOpen.value = false
+ }
+}
+function toggleBrowseMenu() {
+ isBrowseMenuOpen.value = !isBrowseMenuOpen.value
+
+ if (isBrowseMenuOpen.value) {
+ isMobileMenuOpen.value = false
+ }
+}
+function changeTheme() {
+ updateTheme(app.$colorMode.value === 'dark' ? 'light' : 'dark', true)
+}
+
function hideStagingBanner() {
cosmetics.value.hideStagingBanner = true
saveCosmetics()
}
-
diff --git a/pages/moderation/review.vue b/pages/moderation/review.vue
index 5802471976..1aab55cee5 100644
--- a/pages/moderation/review.vue
+++ b/pages/moderation/review.vue
@@ -13,6 +13,13 @@
Sorting by newest
+
+ Start moderating
+
Showing {{ projectsFiltered.length }} {{ projectTypePlural }} of {{ projects.length }} total
@@ -46,7 +53,7 @@
>
- {{ project.title }}
+ {{ project.name }}
{{ $formatProjectType(project.inferred_project_type) }}
@@ -101,6 +108,7 @@ import EyeIcon from '~/assets/images/utils/eye.svg'
import SortAscIcon from '~/assets/images/utils/sort-asc.svg'
import SortDescIcon from '~/assets/images/utils/sort-desc.svg'
import WarningIcon from '~/assets/images/utils/issues.svg'
+import ModerationIcon from '~/assets/images/sidebar/admin.svg'
import Badge from '~/components/ui/Badge.vue'
import { formatProjectType } from '~/plugins/shorthands.js'
@@ -115,7 +123,7 @@ const TIME_24H = 86400000
const TIME_48H = TIME_24H * 2
const { data: projects } = await useAsyncData('moderation/projects?count=1000', () =>
- useBaseFetch('moderation/projects?count=1000')
+ useBaseFetch('moderation/projects?count=1000', { internal: true })
)
const members = ref([])
const projectType = ref('all')
@@ -125,7 +133,7 @@ const projectsFiltered = computed(() =>
projects.value.filter(
(x) =>
projectType.value === 'all' ||
- app.$getProjectTypeForUrl(x.project_type, x.loaders) === projectType.value
+ app.$getProjectTypeForUrl(x.project_types[0], x.loaders) === projectType.value
)
)
@@ -155,7 +163,7 @@ const projectTypes = computed(() => {
})
if (projects.value) {
- const teamIds = projects.value.map((x) => x.team)
+ const teamIds = projects.value.map((x) => x.team_id)
const organizationIds = projects.value.filter((x) => x.organization).map((x) => x.organization)
const url = `teams?ids=${encodeURIComponent(JSON.stringify(teamIds))}`
@@ -169,7 +177,7 @@ if (projects.value) {
projects.value = projects.value.map((project) => {
project.owner = members.value
.flat()
- .find((x) => x.team_id === project.team && x.role === 'Owner')
+ .find((x) => x.team_id === project.team_id && x.role === 'Owner')
project.org = orgs.value.find((x) => x.id === project.organization)
project.age = project.queued ? now - app.$dayjs(project.queued) : Number.MAX_VALUE
project.age_warning = ''
@@ -179,13 +187,29 @@ if (projects.value) {
project.age_warning = 'warning'
}
project.inferred_project_type = app.$getProjectTypeForUrl(
- project.project_type,
+ project.project_types[0],
project.loaders
)
return project
})
}
}
+async function goToProjects() {
+ const router = useRouter()
+
+ const project = projectsFiltered.value[0]
+ await router.push({
+ name: 'type-id',
+ params: {
+ type: project.project_types[0],
+ id: project.slug ? project.slug : project.id,
+ },
+ state: {
+ showChecklist: true,
+ projects: projectsFiltered.value.slice(1).map((x) => (x.slug ? x.slug : x.id)),
+ },
+ })
+}