mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-09-13 19:37:37 -05:00
Merge branch 'dev' into feat/password-editing
This commit is contained in:
@@ -116,7 +116,7 @@ Thanks to the dedicated work by developer [Arian Kordi](https://github.com/arian
|
||||
While the last few months had a lot of focus on internal changes and Juxtaposition, game servers also saw a lot of attention!
|
||||
|
||||
- [Dani](https://github.com/DaniElectra) has begun implementing the [`MessageDelivery` and `Messaging` protocols into our "common" module](https://github.com/PretendoNetwork/nex-protocols-common-go/pull/54). Once complete, these protocols should be widely available to all games
|
||||
- [Dani](https://github.com/DaniElectra) Has begun [implementing the `MatchmakeExtension::GetPlayingSession`](https://github.com/PretendoNetwork/nex-protocols-common-go/pull/55) NEX method for use in multiplayer games
|
||||
- [Dani](https://github.com/DaniElectra) Has begun [implementing the `MatchmakeExtension::GetPlayingSession`](https://github.com/PretendoNetwork/nex-protocols-common-go/pull/55) NEX method for use in multiplayer games
|
||||

|
||||
*Splatoon, for its part, calls GetPlayingSession every time you enter the multiplayer lobby.*
|
||||
- [Trace](https://github.com/TraceEntertains) updated [Yo Kai Watch Blasters](https://github.com/PretendoNetwork/yo-kai-watch-blasters/pull/5) to the latest server libraries, including the above mentioned changes by [Dani](https://github.com/DaniElectra). With this update friend rooms and trading are now functional
|
||||
|
||||
@@ -156,6 +156,7 @@ export default defineNuxtConfig({
|
||||
{ name: 'application-name', content: 'Pretendo Network' },
|
||||
{ name: 'msapplication-TileColor', content: '#1b1f3b' },
|
||||
{ name: 'theme-color', content: '#1b1f3b' },
|
||||
{ property: 'og:title', content: 'Pretendo Network' },
|
||||
{
|
||||
property: 'og:description',
|
||||
content:
|
||||
|
||||
@@ -21,11 +21,12 @@ const errors: Record<string, ApiErrorCodes> = {
|
||||
'INVALID_ARGUMENT: Two or more punctuation characters cannot be used in a row': 'USERNAME_INVALID_CHARS',
|
||||
'INVALID_ARGUMENT: PNID already in use': 'USERNAME_IN_USE',
|
||||
'INVALID_ARGUMENT: Mii name too long': 'MIINAME_TOO_LONG',
|
||||
'INVALID_ARGUMENT: Password must be between 6 and 16 characters long': 'INVALID_PASSWORD_LENGTH',
|
||||
'INVALID_ARGUMENT: Password cannot be the same as username': 'INVALID_PASSWORD_USERNAME',
|
||||
'INVALID_ARGUMENT: Password must have combination of letters, numbers, and/or punctuation characters': 'INVALID_PASSWORD_COMBOS',
|
||||
'INVALID_ARGUMENT: Password may not have 3 repeating characters': 'INVALID_PASSWORD_REPEATING',
|
||||
'INVALID_ARGUMENT: Passwords do not match': 'INVALID_PASSWORD_NO_MATCH'
|
||||
'INVALID_ARGUMENT: Password must be between 6 and 16 characters long': 'PASSWORD_INVALID_LENGTH',
|
||||
'INVALID_ARGUMENT: Password cannot be the same as username': 'PASSWORD_NOT_USERNAME',
|
||||
'INVALID_ARGUMENT: Password must have combination of letters, numbers, and/or punctuation characters': 'PASSWORD_NEEDS_CHARS',
|
||||
'INVALID_ARGUMENT: Password may not have 3 repeating characters': 'PASSWORD_REPEATED_CHARS',
|
||||
'INVALID_ARGUMENT: Passwords do not match': 'PASSWORDS_DO_NOT_MATCH'
|
||||
|
||||
};
|
||||
|
||||
function getCutoffDateForAge(today: Date, age: number) {
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
import { ResetPasswordSchema } from '~~/shared/api-types';
|
||||
import type { ApiErrorCodes } from '~~/shared/errors';
|
||||
|
||||
const errors: Record<string, ApiErrorCodes> = {
|
||||
'Missing token': 'INVALID_INPUT',
|
||||
'Invalid token': 'INVALID_INPUT',
|
||||
'Token expired': 'INVALID_INPUT',
|
||||
'Invalid token. No user found': 'INVALID_INPUT',
|
||||
'Must enter a password': 'PASSWORD_INVALID_LENGTH',
|
||||
'Password is too long': 'PASSWORD_INVALID_LENGTH',
|
||||
'Password is too short': 'PASSWORD_INVALID_LENGTH',
|
||||
'Password cannot be the same as username': 'PASSWORD_NOT_USERNAME',
|
||||
'Password must have combination of letters, numbers, and/or punctuation characters': 'PASSWORD_NEEDS_CHARS',
|
||||
'Password may not have 3 repeating characters': 'PASSWORD_REPEATED_CHARS',
|
||||
'Passwords do not match': 'PASSWORDS_DO_NOT_MATCH'
|
||||
};
|
||||
|
||||
export default defineEventHandler(async (event): Promise<void> => {
|
||||
const body = await readZodBody(event, ResetPasswordSchema);
|
||||
const apiFetch = useHttpApi(event);
|
||||
|
||||
// The GRPC version requires a login token, which the user doesnt have when resetting password, so we're using the HTTP api
|
||||
await apiFetch('/v1/reset-password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
password: body.password,
|
||||
password_confirm: body.passwordConfirm,
|
||||
token: body.resetToken
|
||||
}),
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
try {
|
||||
await apiFetch('/v1/reset-password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
password: body.password,
|
||||
password_confirm: body.passwordConfirm,
|
||||
token: body.resetToken
|
||||
}),
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (err: any) {
|
||||
const data = err?.data;
|
||||
let errorCode: ApiErrorCodes = 'UNHANDLED_ERROR';
|
||||
|
||||
if (typeof data === 'object' && data?.error) {
|
||||
errorCode = errors[data.error] ?? 'UNHANDLED_ERROR';
|
||||
}
|
||||
});
|
||||
|
||||
throw createApiError(errorCode);
|
||||
}
|
||||
});
|
||||
|
||||
18
server/plugins/log-errors.ts
Normal file
18
server/plugins/log-errors.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { H3Error } from 'h3';
|
||||
|
||||
export default defineNitroPlugin((nitroApp) => {
|
||||
nitroApp.hooks.hook('error', (err, { event }) => {
|
||||
const error = err as Partial<H3Error>;
|
||||
const statusCode = error.statusCode ?? 500;
|
||||
|
||||
// Skip expected client errors (404, 401, 422, redirects, ...).
|
||||
if (statusCode < 500) {
|
||||
return;
|
||||
}
|
||||
|
||||
const where = event ? `${event.method} ${event.path}` : 'non-request';
|
||||
const tag = error.unhandled ? '[unhandled]' : '';
|
||||
|
||||
console.error(`[server error] ${tag} ${statusCode} ${where}:`, err);
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,11 @@ export function useHttpApi(event: H3Event, token?: string): HttpApiFetch {
|
||||
if (response.statusCode >= 400) {
|
||||
const err = new Error(`Request failed with ${response.statusCode}`);
|
||||
try {
|
||||
(err as any).data = await response.body.text();
|
||||
if (response.headers['content-type']?.includes('application/json')) {
|
||||
(err as any).data = await response.body.json();
|
||||
} else {
|
||||
(err as any).data = await response.body.text();
|
||||
}
|
||||
} catch {
|
||||
// It's already errored, we don't need to know the body
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ const apiErrorCodes = {
|
||||
USERNAME_INVALID_CHARS: 'Username contains invalid characters',
|
||||
USERNAME_IN_USE: 'PNID already in use',
|
||||
MIINAME_TOO_LONG: 'Mii name too long',
|
||||
INVALID_PASSWORD_LENGTH: 'Password must be between 6 and 16 characters long',
|
||||
INVALID_PASSWORD_USERNAME: 'Password cannot be the same as username',
|
||||
INVALID_PASSWORD_COMBOS: 'Password must have combination of letters, numbers, and/or punctuation characters',
|
||||
INVALID_PASSWORD_REPEATING: 'Password may not have 3 repeating characters',
|
||||
INVALID_PASSWORD_NO_MATCH: 'Passwords do not match',
|
||||
PASSWORD_INVALID_LENGTH: 'Password must be between 6 and 16 characters long',
|
||||
PASSWORD_NOT_USERNAME: 'Password cannot be the same as your username',
|
||||
PASSWORD_NEEDS_CHARS: 'Password must have combination of letters, numbers, and/or punctuation characters',
|
||||
PASSWORDS_DO_NOT_MATCH: 'Passwords do not match',
|
||||
PASSWORD_REPEATED_CHARS: 'Password may not have 3 repeating characters',
|
||||
ACCOUNT_DELETED: 'Account has been deleted',
|
||||
INVALID_ACCESS_LEVEL: 'Invalid access level',
|
||||
BANNED: 'Account is banned',
|
||||
@@ -51,7 +51,11 @@ export const apiErrorCodeStatus: Record<ApiErrorCodes, number> = {
|
||||
UNDER_THIRTEEN: 400,
|
||||
ACCOUNT_DELETED: 400,
|
||||
INVALID_EMAIL: 400,
|
||||
INVALID_PASSWORD_NO_MATCH: 400,
|
||||
PASSWORD_INVALID_LENGTH: 400,
|
||||
PASSWORD_NOT_USERNAME: 400,
|
||||
PASSWORD_NEEDS_CHARS: 400,
|
||||
PASSWORDS_DO_NOT_MATCH: 400,
|
||||
PASSWORD_REPEATED_CHARS: 400,
|
||||
MIINAME_TOO_LONG: 400,
|
||||
USERNAME_IN_USE: 400,
|
||||
USERNAME_INVALID_CHARS: 400,
|
||||
@@ -67,11 +71,7 @@ export const apiErrorCodeStatus: Record<ApiErrorCodes, number> = {
|
||||
INVALID_MII_DATA: 400,
|
||||
EMAIL_UNCHANGED: 400,
|
||||
INVALID_EMAIL_TOKEN: 400,
|
||||
MISSING_EMAIL_TOKEN: 400,
|
||||
INVALID_PASSWORD_LENGTH: 400,
|
||||
INVALID_PASSWORD_USERNAME: 400,
|
||||
INVALID_PASSWORD_COMBOS: 400,
|
||||
INVALID_PASSWORD_REPEATING: 400
|
||||
MISSING_EMAIL_TOKEN: 400
|
||||
};
|
||||
|
||||
export function getTextForApiErrorCode(code: ApiErrorCodes): string {
|
||||
|
||||
36
src/composables/customSeoMeta.ts
Normal file
36
src/composables/customSeoMeta.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { UseSeoMetaInput } from '@unhead/vue';
|
||||
|
||||
export type CustomSeoMetaOptions = UseSeoMetaInput & {
|
||||
subsection?: string;
|
||||
};
|
||||
|
||||
export default function (args?: CustomSeoMetaOptions) {
|
||||
let newTitleTemplate = '';
|
||||
|
||||
switch (args?.subsection) {
|
||||
case ('account'): {
|
||||
newTitleTemplate = 'Account | Pretendo Network';
|
||||
break;
|
||||
}
|
||||
case ('terms'): {
|
||||
newTitleTemplate = 'Terms | Pretendo Network';
|
||||
break;
|
||||
}
|
||||
case ('blog'): {
|
||||
newTitleTemplate = 'Blog | Pretendo Network';
|
||||
break;
|
||||
}
|
||||
case ('docs'): {
|
||||
newTitleTemplate = 'Docs | Pretendo Network';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
newTitleTemplate = 'Pretendo Network';
|
||||
}
|
||||
}
|
||||
|
||||
const newTitle = args?.title ? `${args?.title} | ${newTitleTemplate}` : newTitleTemplate;
|
||||
|
||||
useHead({ title: newTitle });
|
||||
useSeoMeta({ ...args, title: newTitle, ogTitle: newTitle, twitterTitle: newTitle, ogDescription: args?.description, twitterDescription: args?.description, twitterImage: args?.ogImage });
|
||||
}
|
||||
@@ -3,9 +3,11 @@ import { watchImmediate } from '@vueuse/core';
|
||||
import DefaultLayout from './layouts/default.vue';
|
||||
const { error } = defineProps<{ error: any }>();
|
||||
|
||||
watchImmediate([error], () => {
|
||||
console.error(error);
|
||||
});
|
||||
if (import.meta.client) {
|
||||
watchImmediate([() => error], () => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
useHead({
|
||||
titleTemplate: (titleChunk) => {
|
||||
return titleChunk ? `${titleChunk} | Pretendo Network` : 'Pretendo Network';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -3,14 +3,6 @@
|
||||
const path = useRoute().fullPath;
|
||||
|
||||
const isSidebarOpen = ref(false);
|
||||
|
||||
useHead({
|
||||
titleTemplate: (titleChunk) => {
|
||||
return titleChunk
|
||||
? `${titleChunk} | Docs | Pretendo Network`
|
||||
: 'Docs | Pretendo Network';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
"nickname": "昵称",
|
||||
"birthDate": "生日",
|
||||
"gender": "性别",
|
||||
"country": "国家/地区",
|
||||
"country": "国家",
|
||||
"timezone": "时区",
|
||||
"serverEnv": "服务器环境",
|
||||
"production": "生产环境",
|
||||
@@ -244,7 +244,8 @@
|
||||
"no_newsletter_notice": "电子报告目前不可用,请稍后再来查看。",
|
||||
"userSettings": "用户设置",
|
||||
"no_signins_notice": "目前没有任何登录历史。请稍后再试!",
|
||||
"no_edit_from_dashboard": "目前无法从网站修改 PNID 设置。请通过已绑定的主机上修改用户设置。"
|
||||
"no_edit_from_dashboard": "目前无法从网站修改 PNID 设置。请通过已绑定的主机上修改用户设置。",
|
||||
"region": "地区"
|
||||
},
|
||||
"upgrade": "升级账号",
|
||||
"unavailable": "无法使用",
|
||||
@@ -309,7 +310,7 @@
|
||||
"clickToSet": "点击设置",
|
||||
"favorite": "设为最爱",
|
||||
"sharing": "共享",
|
||||
"copying": "复制中",
|
||||
"copying": "允许复制",
|
||||
"save": "保存",
|
||||
"saveCaption": "保存你的 Mii 虚拟形象会删除之前的 Mii 虚拟形象。",
|
||||
"noCanvas": "您的浏览器不支持 canvas 元素。",
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
"profile": "個人檔案",
|
||||
"birthDate": "生日",
|
||||
"gender": "性別",
|
||||
"country": "國家/地區",
|
||||
"country": "國家",
|
||||
"timezone": "時區",
|
||||
"serverEnv": "伺服器環境",
|
||||
"production": "生產",
|
||||
@@ -204,7 +204,8 @@
|
||||
"passwordPrompt": "輸入你的 PNID 密碼以下載 Cemu 檔案",
|
||||
"no_signins_notice": "登入歷史紀錄目前未記錄。請稍後再來查看!",
|
||||
"no_newsletter_notice": "電子報告目前無法使用。請稍後再來查看!",
|
||||
"no_edit_from_dashboard": "目前無法從網站編輯 PNID 設定。請通過已連結的主機上編輯使用者設定。"
|
||||
"no_edit_from_dashboard": "目前無法從網站編輯 PNID 設定。請通過已連結的主機上編輯使用者設定。",
|
||||
"region": "地區"
|
||||
},
|
||||
"upgrade": "升級帳號",
|
||||
"unavailable": "無法使用",
|
||||
@@ -307,7 +308,7 @@
|
||||
"miiEditor": {
|
||||
"birthday": "生日",
|
||||
"clickToSet": "點觸設定",
|
||||
"copying": "複製中",
|
||||
"copying": "允許複製",
|
||||
"corruptedData": "找到已損壞的 Mii 資料。是否要從頭開始?",
|
||||
"creator": "創作者",
|
||||
"favorite": "設為最愛",
|
||||
|
||||
@@ -48,6 +48,8 @@ const { execute, isLoading } = useAsync({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
customSeoMeta({ subsection: 'account', title: 'Forgot password' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -155,9 +155,7 @@ const { execute: executeUnlinkDiscord, isLoading: isLoadingUnlink } = useAsync({
|
||||
}
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: `Account`
|
||||
});
|
||||
customSeoMeta({ subsection: 'account' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -167,15 +165,27 @@ useHead({
|
||||
>
|
||||
<div class="account-sidebar">
|
||||
<div class="user">
|
||||
<a
|
||||
href="/account/miieditor"
|
||||
class="mii"
|
||||
>
|
||||
<img
|
||||
:src="profile.mii?.imageUrl"
|
||||
alt="Mii image"
|
||||
<div class="mii-wrapper">
|
||||
<a
|
||||
href="/account/miieditor"
|
||||
class="mii"
|
||||
>
|
||||
</a>
|
||||
<img
|
||||
:src="profile.mii?.imageUrl"
|
||||
alt="Mii image"
|
||||
>
|
||||
</a>
|
||||
<a
|
||||
class="edit-button"
|
||||
href="/account/miieditor"
|
||||
>
|
||||
<Icon
|
||||
name="ph:pencil"
|
||||
size="20"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="miiname">
|
||||
{{ profile.mii?.name }}
|
||||
</p>
|
||||
@@ -641,36 +651,41 @@ useHead({
|
||||
border-color: #5aff15;
|
||||
}
|
||||
|
||||
.account-sidebar .user .mii-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.account-sidebar .user a.mii {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
overflow: hidden;
|
||||
border-radius: 100%;
|
||||
border-radius: 9999px;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
.account-sidebar .user a.mii::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
no-repeat center/50% url("@/public/assets/images/edit.svg"),
|
||||
rgba(55, 60, 101, 0.7);
|
||||
opacity: 0;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
.account-sidebar .user a.mii:hover::after {
|
||||
opacity: 1;
|
||||
.account-sidebar .user a.mii:hover,
|
||||
.account-sidebar .user .mii-wrapper a.edit-button:hover,
|
||||
.account-sidebar .user:has(a.edit-button:hover) a.mii,
|
||||
.account-sidebar .user:has(a.mii:hover) a.edit-button {
|
||||
background: var(--bg-shade-4);
|
||||
}
|
||||
|
||||
.account-sidebar .user .mii {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.account-sidebar .user .mii-wrapper a.edit-button {
|
||||
color: inherit;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-shade-3);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid var(--bg-shade-1);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.account-sidebar .buttons {
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
|
||||
@@ -47,6 +47,8 @@ const { execute, isLoading } = useAsync({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
customSeoMeta({ subsection: 'account', title: 'Login' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -15,9 +15,7 @@ definePageMeta({
|
||||
|
||||
const { data: profile } = await useApiFetch('/api/auth/me');
|
||||
|
||||
useHead({
|
||||
title: 'Mii Editor | Pretendo Network'
|
||||
});
|
||||
customSeoMeta({ subsection: 'account', title: 'Mii Editor' });
|
||||
|
||||
const beforeUnload = (e: BeforeUnloadEvent) => {
|
||||
e?.preventDefault();
|
||||
|
||||
@@ -55,6 +55,8 @@ const { execute, isLoading } = useAsync({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
customSeoMeta({ subsection: 'account', title: 'Register' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -30,6 +30,8 @@ const { execute, isLoading } = useAsync({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
customSeoMeta({ subsection: 'account', title: 'Reset Password' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -81,10 +81,7 @@ function hasSubscription(priceId: string) {
|
||||
profile.value?.stripeTier && priceId === profile.value.stripeTier.priceId
|
||||
);
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `Upgrade`
|
||||
});
|
||||
customSeoMeta({ subsection: 'account', title: 'Upgrade' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -8,16 +8,11 @@ if (!post.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' });
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `${post.value.title} | Blog`,
|
||||
meta: [
|
||||
{ property: 'description', content: post.value.caption },
|
||||
{ property: 'og:description', content: post.value.caption },
|
||||
{ property: 'og:image', content: post.value.cover_image },
|
||||
{ property: 'og:image:alt', content: '' },
|
||||
{ name: 'twitter:description', content: post.value.caption },
|
||||
{ name: 'twitter:image', content: post.value.cover_image }
|
||||
]
|
||||
customSeoMeta({
|
||||
subsection: 'blog',
|
||||
title: post.value.title,
|
||||
description: post.value.caption,
|
||||
ogImage: post.value.cover_image
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
useHead({
|
||||
title: 'Blog'
|
||||
});
|
||||
customSeoMeta({ subsection: 'blog' });
|
||||
/* eslint-disable vue/no-v-html -- we might wanna avoid this by rewriting the locales to use variables */
|
||||
const { data: allPosts } = await useAsyncData('blog', () => queryCollection('blog').all());
|
||||
|
||||
const posts = computed(() => allPosts.value?.filter(p => !p.path.startsWith('/blog/_')).sort((a, b) => {
|
||||
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
||||
}));
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -7,15 +7,10 @@ const { data: doc } = await useAsyncData(`docs-${slug}`, () => {
|
||||
if (!doc.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' });
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `${doc.value.title}`,
|
||||
meta: [
|
||||
{ property: 'description', content: doc.value.description },
|
||||
{ property: 'og:description', content: doc.value.description },
|
||||
{ property: 'og:image:alt', content: '' },
|
||||
{ name: 'twitter:description', content: doc.value.description }
|
||||
]
|
||||
customSeoMeta({
|
||||
subsection: 'docs',
|
||||
title: doc.value.title,
|
||||
description: doc.value.description
|
||||
});
|
||||
|
||||
definePageMeta({
|
||||
|
||||
@@ -8,14 +8,10 @@ if (!error.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' });
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `${error.value.code} | Docs`,
|
||||
meta: [
|
||||
{ property: 'description', content: error.value.message },
|
||||
{ property: 'og:description', content: error.value.message },
|
||||
{ property: 'og:image:alt', content: '' },
|
||||
{ name: 'twitter:description', content: error.value.message }
|
||||
]
|
||||
customSeoMeta({
|
||||
subsection: 'docs',
|
||||
title: error.value.code,
|
||||
description: error.value.message
|
||||
});
|
||||
|
||||
definePageMeta({
|
||||
|
||||
@@ -36,25 +36,10 @@ const { data: searchResults } = await useAsyncData(
|
||||
);
|
||||
const results = computed(() => searchResults.value ?? []);
|
||||
|
||||
useHead({
|
||||
customSeoMeta({
|
||||
subsection: 'docs',
|
||||
title: 'Search',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'Got an error code? Find solutions here.'
|
||||
},
|
||||
{
|
||||
name: 'og:description',
|
||||
content:
|
||||
'Got an error code? Find solutions here.'
|
||||
},
|
||||
{
|
||||
name: 'twitter:description',
|
||||
content:
|
||||
'Got an error code? Find solutions here.'
|
||||
}
|
||||
]
|
||||
description: 'Got an error code? Find solutions here.'
|
||||
});
|
||||
|
||||
definePageMeta({
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
useHead({
|
||||
|
||||
customSeoMeta({
|
||||
subsection: 'docs',
|
||||
title: 'Install',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'Learn how to set up Pretendo on a variety of consoles and emulators.'
|
||||
},
|
||||
{
|
||||
name: 'og:description',
|
||||
content:
|
||||
'Learn how to set up Pretendo on a variety of consoles and emulators.'
|
||||
},
|
||||
{
|
||||
name: 'twitter:description',
|
||||
content:
|
||||
'Learn how to set up Pretendo on a variety of consoles and emulators.'
|
||||
}
|
||||
]
|
||||
description: 'Learn how to set up Pretendo Network on a variety of consoles and emulators.'
|
||||
});
|
||||
|
||||
definePageMeta({
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
useHead({
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'Learn how to set up Pretendo on a variety of consoles and emulators.'
|
||||
},
|
||||
{
|
||||
name: 'og:description',
|
||||
content:
|
||||
'Learn how to set up Pretendo on a variety of consoles and emulators.'
|
||||
},
|
||||
{
|
||||
name: 'twitter:description',
|
||||
content:
|
||||
'Learn how to set up Pretendo on a variety of consoles and emulators.'
|
||||
}
|
||||
]
|
||||
customSeoMeta({
|
||||
subsection: 'docs',
|
||||
description: 'Learn how to set up Pretendo Network on a variety of consoles and emulators.'
|
||||
});
|
||||
|
||||
definePageMeta({
|
||||
|
||||
@@ -32,6 +32,8 @@ function arraySplit<T>(array: Array<T>, groupCount: number): Array<Array<T>> {
|
||||
function titleSuffixHandler(path: string) {
|
||||
return te(path) ? t(path) : null;
|
||||
}
|
||||
|
||||
customSeoMeta();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -4,6 +4,7 @@ useHead({
|
||||
link: [{ rel: 'stylesheet', href: 'https://use.typekit.net/gok5tsu.css' }]
|
||||
});
|
||||
|
||||
customSeoMeta();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n();
|
||||
|
||||
customSeoMeta({
|
||||
title: t('progressPage.title'),
|
||||
description: t('progressPage.description')
|
||||
});
|
||||
|
||||
const progress = await useFetch('/api/progress');
|
||||
const projects = computed(() => progress.data.value?.items ?? []);
|
||||
const donations = computed(() => progress.data.value?.donations);
|
||||
|
||||
@@ -8,14 +8,10 @@ if (!termContent.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' });
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `${termContent.value.title} | Terms`,
|
||||
meta: [
|
||||
{ property: 'description', content: termContent.value.description },
|
||||
{ property: 'og:description', content: termContent.value.description },
|
||||
{ property: 'og:image:alt', content: '' },
|
||||
{ name: 'twitter:description', content: termContent.value.description }
|
||||
]
|
||||
customSeoMeta({
|
||||
subsection: 'terms',
|
||||
title: termContent.value.title,
|
||||
description: termContent.value.description
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user