Merge branch 'dev' into feat/password-editing
Some checks failed
Build and Publish Docker Image / Build and Publish (amd64) (push) Has been cancelled
Build and Publish Docker Image / Build and Publish (arm64) (push) Has been cancelled

This commit is contained in:
limes
2026-09-03 19:06:17 +02:00
14 changed files with 112 additions and 54 deletions

19
src/components/MdLink.vue Normal file
View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
function fixFragmentLinks(e: MouseEvent) {
const targetEl = e.target as HTMLAnchorElement;
const to = targetEl?.getAttribute('href');
if (targetEl?.tagName !== 'A' || !to?.startsWith('#')) {
return;
}
e.preventDefault();
document.getElementById(to.slice(1))?.scrollIntoView();
}
</script>
<template>
<NuxtLink @click="fixFragmentLinks">
<slot />
</NuxtLink>
</template>

View File

@@ -5,6 +5,16 @@ import type { ApiAccountEmailUpdateRequest, GetApiAuthMe } from '~~/shared/api-t
const toasts = useToasts();
const open = defineModel<boolean>();
const updateOpen = ref(false);
const verifyOpen = ref(false);
watch(updateOpen, () => {
open.value = updateOpen.value;
});
watch(verifyOpen, () => {
open.value = verifyOpen.value;
});
const newEmail = ref('');
const { profile } = defineProps<{
@@ -47,8 +57,7 @@ const {
<template>
<Dialog.Root
v-if="profile.emailValidated"
v-model:open="open"
v-model:open="updateOpen"
>
<Dialog.Portal :to="dialogContainer ?? undefined">
<Dialog.Overlay />
@@ -106,8 +115,8 @@ const {
</Dialog.Root>
<Dialog.Root
v-else
v-model:open="open"
v-if="!profile.emailValidated"
v-model:open="verifyOpen"
>
<Dialog.Portal :to="dialogContainer ?? undefined">
<Dialog.Overlay />
@@ -137,9 +146,12 @@ const {
email: profile.emailAddress
})"
>
{{
$t("account.settings.unverifiedEmailModal.resend")
}}
<Loader v-if="isLoadingUpdateEmail" />
<span v-else>
{{
$t("account.settings.unverifiedEmailModal.resend")
}}
</span>
</button>
</p>
</Dialog.Description>
@@ -153,9 +165,14 @@ const {
</Dialog.Close>
</div>
</Dialog.Content>
</Dialog.Portal><Dialog.Trigger class="unverified">
{{ $t('account.settings.verify_email') }}
</Dialog.Trigger>
</Dialog.Portal>
<p class="notice">
{{ $t('account.settings.verify_email_notice') }}
<br>
<Dialog.Trigger class="unverified">
{{ $t('account.settings.verify_email_button') }}
</Dialog.Trigger>
</p>
</Dialog.Root>
</template>
<style>
@@ -185,4 +202,9 @@ fieldset {
gap: .25rem;
border: none;
}
.notice {
font-size: .9em;
margin-top: 1rem !important;
}
</style>

View File

@@ -220,7 +220,8 @@
"update": "Update",
"unverified_email": "Unverified",
"verified_email": "Verified",
"verify_email": "Verify email",
"verify_email_notice": "You haven't verified your email address yet.",
"verify_email_button": "Verify now",
"settingCards": {
"userSettings": "User settings",
"profile": "Profile",
@@ -281,6 +282,11 @@
"successNotice": "Password updated successfully"
}
},
"emailVerification": {
"verifiedTitle": "Email successfully verified",
"verifiedCaption": "You may now close this tab.",
"failedTitle": "Failed to verify email"
},
"accountLevel": [
"Standard",
"Tester",

View File

@@ -27,36 +27,31 @@ const {
}
});
await callOnce(async () => {
await executeVerifyEmail();
callOnce(() => {
executeVerifyEmail();
});
</script>
<template>
<div v-if="isLoadingVerifyEmail">
<div class="account-form-wrapper">
<form
class="account loading"
>
<Loader />
</form>
<div class="account-form-wrapper loading">
<Loader />
</div>
</div>
<div v-else-if="success">
<div class="account-form-wrapper">
<h1
class="title dot"
data-title-suffix="!"
>
Email successfully verified{{ '' }}
{{ $t('account.emailVerification.verifiedTitle') }}
</h1>
<p>You may now close this tab.</p>
<p>{{ $t('account.emailVerification.verifiedCaption') }}</p>
</div>
</div>
<div v-else>
<div class="account-form-wrapper">
<h1 class="title dot">
Failed to verify email{{ '' }}
{{ $t('account.emailVerification.failedTitle') }}
</h1>
<p>{{ errorMessage }}</p>
</div>
@@ -74,6 +69,9 @@ await callOnce(async () => {
width: fit-content;
overflow: hidden;
}
.account-form-wrapper.loading {
justify-content: center;
}
.title {
font-size: 4rem;
margin-bottom: 0;

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import MdLink from '~/components/MdLink.vue';
const slug = useRoute().params.slug;
const { data: post } = await useAsyncData(`blog-${slug}`, () => {
return queryCollection('blog').path(`/blog/${slug}`).first();
@@ -41,7 +43,10 @@ customSeoMeta({
</span>
</div>
<ContentRenderer :value="post" />
<ContentRenderer
:value="post"
:components="{ 'a': MdLink }"
/>
</div>
</div>
</template>

View File

@@ -1,5 +1,8 @@
<script setup lang="ts">
import MdLink from '~/components/MdLink.vue';
const slug = (useRoute().params.slug as string[]).join('/');
const { data: doc } = await useAsyncData(`docs-${slug}`, () => {
return queryCollection('docs').path(`/docs/${slug}`).first();
});
@@ -7,6 +10,7 @@ const { data: doc } = await useAsyncData(`docs-${slug}`, () => {
if (!doc.value) {
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' });
}
customSeoMeta({
subsection: 'docs',
title: doc.value.title,
@@ -23,6 +27,7 @@ definePageMeta({
<ContentRenderer
v-if="doc"
:value="doc"
:components="{ 'a': MdLink }"
/>
</div>
</template>

View File

@@ -1,4 +1,6 @@
<script lang="ts" setup>
import MdLink from '~/components/MdLink.vue';
const slug = useRoute().params.slug;
const { data: termContent } = await useAsyncData(`terms-${slug}`, () => {
return queryCollection('terms').path(`/terms/${slug}`).first();
@@ -21,6 +23,7 @@ customSeoMeta({
<ContentRenderer
v-if="termContent"
:value="termContent"
:components="{ 'a': MdLink }"
/>
</div>
</div>