+
+
+ Download Paper Plugins
+ Download Velocity Plugins
+ Download Waterfall Plugins
+
© {{ new Date().getFullYear() }} PaperMC
diff --git a/frontend/src/components/modals/Modal.vue b/frontend/src/components/modals/Modal.vue
index bb3ecae7c..281784e6b 100644
--- a/frontend/src/components/modals/Modal.vue
+++ b/frontend/src/components/modals/Modal.vue
@@ -60,7 +60,7 @@ defineExpose({
-
{{ props.title }}
+
{{ props.title }}
diff --git a/frontend/src/components/projects/MemberList.vue b/frontend/src/components/projects/MemberList.vue
index 4ff239732..06d552228 100644
--- a/frontend/src/components/projects/MemberList.vue
+++ b/frontend/src/components/projects/MemberList.vue
@@ -143,7 +143,7 @@ async function doSearch(val?: string) {
-
{{ i18n.t("project.members") }}
+ {{ i18n.t("project.members") }}
{{ i18n.t("form.memberList.info") }}
diff --git a/frontend/src/components/projects/ProjectCard.vue b/frontend/src/components/projects/ProjectCard.vue
index d54a8fe4b..659fde5d0 100644
--- a/frontend/src/components/projects/ProjectCard.vue
+++ b/frontend/src/components/projects/ProjectCard.vue
@@ -24,7 +24,7 @@ defineProps<{
-
+
{{ project.name }}
{{ i18n.t("general.by") }}
@@ -34,12 +34,12 @@ defineProps<{
-
+
-
{{ project.description }}
+
{{ project.description }}
{{ i18n.t("project.category." + project.category) }}
diff --git a/frontend/src/components/projects/ProjectInfo.vue b/frontend/src/components/projects/ProjectInfo.vue
index 187d57ee1..e742a539b 100644
--- a/frontend/src/components/projects/ProjectInfo.vue
+++ b/frontend/src/components/projects/ProjectInfo.vue
@@ -11,7 +11,7 @@ const namespace = computed(() => props.project.namespace.owner + "/" + props.pro
- {{ i18n.t("project.info.title") }}
+ {{ i18n.t("project.info.title") }}
diff --git a/frontend/src/components/projects/ProjectPageList.vue b/frontend/src/components/projects/ProjectPageList.vue
index 4cce1e67d..c11fb0a7b 100644
--- a/frontend/src/components/projects/ProjectPageList.vue
+++ b/frontend/src/components/projects/ProjectPageList.vue
@@ -14,7 +14,7 @@ const route = useRoute("user-project");
-
{{ i18n.t("page.plural") }}
+ {{ i18n.t("page.plural") }}
diff --git a/frontend/src/components/projects/ProjectPageMarkdown.vue b/frontend/src/components/projects/ProjectPageMarkdown.vue
index 274cfcdff..00f20f783 100644
--- a/frontend/src/components/projects/ProjectPageMarkdown.vue
+++ b/frontend/src/components/projects/ProjectPageMarkdown.vue
@@ -12,9 +12,8 @@ const router = useRouter();
const updateProjectPages = inject<(pages: HangarProjectPage[]) => void>("updateProjectPages");
const { editingPage, changeEditingPage, page, savePage, deletePage } = await useProjectPage(route, router, props.project, props.mainPage);
-if (page) {
- const title = props.mainPage ? props.project.name : page.value?.name + " | " + props.project.name;
- useHead(useSeo(title, props.project.description, route, props.project.avatarUrl));
+if (page && !props.mainPage) {
+ useHead(useSeo(page.value?.name + " | " + props.project.name, props.project.description, route, props.project.avatarUrl));
}
async function deletePageAndUpdateProject() {
diff --git a/frontend/src/composables/useMarked.ts b/frontend/src/composables/useMarked.ts
index 1c42a7ad5..0b2dc3a13 100644
--- a/frontend/src/composables/useMarked.ts
+++ b/frontend/src/composables/useMarked.ts
@@ -32,6 +32,7 @@ const renderer = {
const id = `${slugger.slug(raw)}`;
const heading = { level, text, id };
headings.push(heading);
+ level = level + 1;
return `
${text}
diff --git a/frontend/src/composables/useSeo.ts b/frontend/src/composables/useSeo.ts
index c75281f5d..e71789958 100644
--- a/frontend/src/composables/useSeo.ts
+++ b/frontend/src/composables/useSeo.ts
@@ -7,14 +7,17 @@ export function useSeo(
description: string | TranslateResult | null | undefined,
route: RouteLocationNormalized,
image: string | null,
- additionalScripts?: { type: string; children: string; key: string }[]
+ additionalScripts?: { type: string; children: string; key: string }[],
+ manualTitle?: boolean
): UseHeadInput {
description = description || "Plugin repository for Paper, Velocity, Waterfall and Folia.";
const config = useConfig();
const canonical = config.publicHost + (route.fullPath.endsWith("/") ? route.fullPath.substring(0, route.fullPath.length - 1) : route.fullPath);
image = image || "https://docs.papermc.io/img/paper.png";
image = image.startsWith("http") ? image : config.publicHost + image;
- title = title ? title + " | Hangar" : "Hangar";
+ if (!manualTitle) {
+ title = title ? title + " | Hangar" : "Hangar";
+ }
useSeoMeta({
title,
diff --git a/frontend/src/composables/useUrlHelper.ts b/frontend/src/composables/useUrlHelper.ts
index c6faea19d..50b460cf7 100644
--- a/frontend/src/composables/useUrlHelper.ts
+++ b/frontend/src/composables/useUrlHelper.ts
@@ -9,6 +9,9 @@ const isSafeHost = (host: string) => {
};
const isSafe = (urlString: string) => {
+ if (!urlString) {
+ return false;
+ }
if (urlString.startsWith("#") || urlString.startsWith("/")) {
return true;
}
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 8f4a344b8..254156a57 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -42,7 +42,7 @@
},
"hangar": {
"footer": {
- "api": "API",
+ "api": "Hangar API",
"org": "GitHub",
"terms": "Terms and Conditions",
"privacypolicy": "Privacy Policy",
@@ -55,8 +55,6 @@
"sortBy": "Sort by",
"noProjects": "There are no projects. 😢",
"noProjectsFound": "Found 0 projects. 😢",
- "title": "Find your favorite plugins",
- "subTitle": "Hangar allows you to find the best Paper, Velocity, or Waterfall plugins for your Minecraft server.",
"categories": "Categories",
"licenses": "Licenses",
"versions": {
@@ -535,7 +533,7 @@
}
},
"page": {
- "subheader": "{0} released this version: {1}",
+ "subheader": "{0} released {2} version {3} on {1}",
"dependencies": "Dependencies",
"platforms": "Platforms",
"required": "(required)",
diff --git a/frontend/src/pages/[user]/[project]/index.vue b/frontend/src/pages/[user]/[project]/index.vue
index 986d8a89a..4eaf2a65e 100644
--- a/frontend/src/pages/[user]/[project]/index.vue
+++ b/frontend/src/pages/[user]/[project]/index.vue
@@ -1,17 +1,20 @@
@@ -69,9 +101,9 @@ function createPinnedVersionUrl(version: PinnedVersion): string {
>
-
{{ i18n.t("project.sponsors") }}
+ {{ i18n.t("project.sponsors") }}
- {{ i18n.t("project.sponsorsTooltip") }}
+ {{ i18n.t("project.sponsorsTooltip") }}
@@ -92,7 +124,7 @@ function createPinnedVersionUrl(version: PinnedVersion): string {
- {{ i18n.t("project.pinnedVersions") }}
+ {{ i18n.t("project.pinnedVersions") }}
@@ -129,7 +161,7 @@ function createPinnedVersionUrl(version: PinnedVersion): string {
- {{ section.title }}
+ {{ section.title }}
diff --git a/frontend/src/pages/[user]/[project]/versions/[version]/index.vue b/frontend/src/pages/[user]/[project]/versions/[version]/index.vue
index fcd635034..83c19e6c3 100644
--- a/frontend/src/pages/[user]/[project]/versions/[version]/index.vue
+++ b/frontend/src/pages/[user]/[project]/versions/[version]/index.vue
@@ -2,11 +2,13 @@
import type { AxiosError } from "axios";
import { ReviewState, PinnedStatus, NamedPermission, Visibility } from "~/types/backend";
import type { Platform, HangarProject, HangarVersion, User } from "~/types/backend";
+import { titleCase } from "scule";
const route = useRoute("user-project-versions-version");
const i18n = useI18n();
const router = useRouter();
const notification = useNotificationStore();
+const config = useConfig();
const props = defineProps<{
version: HangarVersion;
@@ -53,7 +55,50 @@ function sortedDependencies(platform: Platform) {
return [];
}
-useHead(useSeo(props.project?.name + " " + projectVersion.value?.name, props.project.description, route, props.project.avatarUrl));
+const supportsString = computed(() => {
+ const result = [];
+ for (let platform in projectVersion.value?.platformDependenciesFormatted) {
+ result.push(titleCase(platform.toLowerCase()) + " " + projectVersion.value?.platformDependenciesFormatted[platform]);
+ }
+ return result.join(", ");
+});
+useHead(
+ useSeo(
+ props.project?.name + " " + projectVersion.value?.name,
+ `Download ${props.project?.name} ${projectVersion.value?.name} on Hangar.
+ Supports ${supportsString.value}.
+ Published on ${lastUpdated(new Date(projectVersion.value?.createdAt))}.
+ ${projectVersion.value?.stats?.totalDownloads} downloads.`,
+ route,
+ props.project.avatarUrl,
+ [
+ {
+ type: "application/ld+json",
+ children: JSON.stringify({
+ "@context": "https://schema.org",
+ "@type": "WebContent",
+ about: {
+ "@type": "WebContent",
+ name: props.project?.name,
+ url: config.publicHost + "/" + props.project?.namespace?.owner + "/" + props.project?.namespace?.slug,
+ description: props.project?.description,
+ },
+ author: {
+ "@type": "Person",
+ name: props.project?.namespace.owner,
+ url: config.publicHost + "/" + props.project?.namespace?.owner,
+ },
+ name: props.project?.name + " " + projectVersion.value?.name,
+ datePublished: projectVersion.value?.createdAt,
+ dateCreated: projectVersion.value?.createdAt,
+ version: projectVersion.value?.name,
+ url: config.publicHost + route.fullPath,
+ }),
+ key: "version",
+ },
+ ]
+ )
+);
async function savePage(content: string) {
try {
@@ -138,7 +183,9 @@ async function restoreVersion() {
- {{ i18n.t("version.page.subheader", [projectVersion.author, lastUpdated(new Date(projectVersion.createdAt))]) }}
+ {{
+ i18n.t("version.page.subheader", [projectVersion.author, lastUpdated(new Date(projectVersion.createdAt)), project.name, projectVersion.name])
+ }}
@@ -179,7 +226,7 @@ async function restoreVersion() {
- {{ i18n.t("version.page.manage") }}
+ {{ i18n.t("version.page.manage") }}
@@ -256,7 +303,7 @@ async function restoreVersion() {
-
{{ i18n.t("project.info.title") }}
+ {{ i18n.t("project.info.title") }}
@@ -271,7 +318,7 @@ async function restoreVersion() {
{{ i18n.t(hasPerms(NamedPermission.IsSubjectMember) ? "project.info.totalTotalDownloads" : "project.info.totalDownloads", 0) }}
- {{ version.stats.totalDownloads.toLocaleString("en-US") }}
+ {{ projectVersion.stats.totalDownloads.toLocaleString("en-US") }}
@@ -281,7 +328,7 @@ async function restoreVersion() {
{{ i18n.t("project.info.totalDownloads", 0) }}
- {{ version.stats.platformDownloads[platform].toLocaleString("en-US") }}
+ {{ projectVersion.stats.platformDownloads[platform].toLocaleString("en-US") }}
@@ -291,7 +338,7 @@ async function restoreVersion() {
-
{{ i18n.t("version.page.platforms") }}
+ {{ i18n.t("version.page.platforms") }}
@@ -312,7 +359,7 @@ async function restoreVersion() {
-
{{ i18n.t("version.page.dependencies") }}
+ {{ i18n.t("version.page.dependencies") }}
diff --git a/frontend/src/pages/[user]/[project]/versions/index.vue b/frontend/src/pages/[user]/[project]/versions/index.vue
index b5da2c094..c5d42c758 100644
--- a/frontend/src/pages/[user]/[project]/versions/index.vue
+++ b/frontend/src/pages/[user]/[project]/versions/index.vue
@@ -1,5 +1,5 @@
@@ -108,6 +143,7 @@ useHead(useSeo(props.user.name, description, route, props.user.avatarUrl));
+ {{ user.name }}'s Plugins
(page = newPage)" />
@@ -115,7 +151,9 @@ useHead(useSeo(props.user.name, description, route, props.user.avatarUrl));
v-if="buttons.length !== 0 || (organization && hasPerms(NamedPermission.IsSubjectOwner))"
class="mb-4 border-solid border-top-4 border-top-red-500 dark:border-top-red-500"
>
-
{{ i18n.t("author.management") }}
+
+ {{ i18n.t("author.management") }}
+
@@ -130,7 +168,9 @@ useHead(useSeo(props.user.name, description, route, props.user.avatarUrl));
{{ i18n.t(`author.tooltips.${btn.name}`) }}
-
+
+
+
@@ -139,7 +179,7 @@ useHead(useSeo(props.user.name, description, route, props.user.avatarUrl));
- Shares address with
+ Shares address with
@@ -153,7 +193,7 @@ useHead(useSeo(props.user.name, description, route, props.user.avatarUrl));
-
{{ i18n.t("author.orgs") }}
+
{{ user.name }}'s {{ i18n.t("author.orgs") }}
- {{ i18n.t("author.stars") }}
+
+ {{ i18n.t("author.stars") }}
+
@@ -195,7 +237,9 @@ useHead(useSeo(props.user.name, description, route, props.user.avatarUrl));
- {{ i18n.t("author.watching") }}
+
+ {{ i18n.t("author.watching") }}
+
diff --git a/frontend/src/pages/index.vue b/frontend/src/pages/index.vue
index 32ee3535b..c8d2dc46f 100644
--- a/frontend/src/pages/index.vue
+++ b/frontend/src/pages/index.vue
@@ -1,261 +1,3 @@
-
-
-
-
- {{ i18n.t("hangar.projectSearch.title") }}
- {{ i18n.t("hangar.projectSearch.subTitle") }}
-
-
-
-
-
-
-
- {{ i18n.t("hangar.projectSearch.sortBy") }}
-
-
-
-
-
-
- {{ sorter.label }}
-
-
-
-
-
-
-
-
-
-
- {{ sorter.label }}
-
-
-
-
-
-
-
-
-
-
-
-
{{ i18n.t("hangar.projectSearch.versions." + filters.platform) }}
-
-
-
-
-
-
-
{{ i18n.t("hangar.projectSearch.categories") }}
-
-
-
-
-
-
-
-
-
+
diff --git a/frontend/src/pages/paper.vue b/frontend/src/pages/paper.vue
new file mode 100644
index 000000000..27148065e
--- /dev/null
+++ b/frontend/src/pages/paper.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/frontend/src/pages/velocity.vue b/frontend/src/pages/velocity.vue
new file mode 100644
index 000000000..79d18d997
--- /dev/null
+++ b/frontend/src/pages/velocity.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/frontend/src/pages/waterfall.vue b/frontend/src/pages/waterfall.vue
new file mode 100644
index 000000000..1e3ee30b4
--- /dev/null
+++ b/frontend/src/pages/waterfall.vue
@@ -0,0 +1,8 @@
+
+
+
+
+