PronounsPage/pages/blog/[slug].vue
2025-01-12 14:12:15 +01:00

174 lines
4.8 KiB
Vue

<script setup lang="ts">
import Columnist from 'avris-columnist';
import { useNuxtApp, useRoute } from 'nuxt/app';
import { computed, ref } from 'vue';
import useConfig from '~/composables/useConfig.ts';
import useSimpleHead from '~/composables/useSimpleHead.ts';
import parseMarkdown from '~/src/parseMarkdown.ts';
import type { MarkdownInfo } from '~/src/parseMarkdown.ts';
definePageMeta({
translatedPaths: (config) => {
if (!config.links.enabled || !config.links.blog) {
return [];
}
const shortcuts = Object.entries(config.blog?.shortcuts ?? {})
.filter(([_shortcut, slug]) => !(config.blog?.keepFullPath ?? []).includes(slug))
.map(([shortcut, slug]) => {
const shortcutRoute = { paths: [`/${encodeURIComponent(shortcut)}`], meta: { slug } };
return [`blogEntryShortcut:${shortcut}`, shortcutRoute] as const;
});
return {
blogEntry: { paths: [`/${encodeURIComponent(config.links.blogRoute)}/:slug`] },
...Object.fromEntries(shortcuts),
};
},
});
const { $translator: translator, $loadScript: loadScript } = useNuxtApp();
const route = useRoute();
const slug = (route.params.slug ?? route.meta.slug) as string;
const blogEntry = ref<MarkdownInfo>({ title: null, img: null, intro: null, content: null });
const title = computed(() => blogEntry.value.title);
const banner = computed(() => blogEntry.value.img);
const description = computed(() => blogEntry.value.intro);
const content = computed(() => blogEntry.value.content);
useSimpleHead({
title,
description,
banner,
}, translator);
const post = useTemplateRef<HTMLDivElement>('post');
try {
const markdown = (await import(`~/data/blog/${slug}.md`)).default;
blogEntry.value = await parseMarkdown(markdown, translator);
} catch (error) {
// import failed
}
const config = useConfig();
onMounted(async () => {
if (!content.value) {
return;
}
if (content.value.includes('twitter-tweet')) {
await loadScript('twitter', 'https://platform.twitter.com/widgets.js');
}
if (content.value.includes('fb-post')) {
const divElement = document.createElement('div');
divElement.id = 'fb-root';
document.body.appendChild(divElement);
await loadScript('facebook', 'https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v19.0', 'xwbrn1J4');
}
for (const wall of post.value?.querySelectorAll('.columnist-wall') ?? []) {
const columnist = new Columnist(wall);
columnist.start();
}
});
</script>
<template>
<Page>
<NotFound v-if="!content" />
<div v-else ref="post" class="blog-post">
<p v-if="!config.links.split">
<nuxt-link v-if="config.links.blog" :to="{ name: 'blog' }">
<Icon v="pen-nib" />
<T>links.blog</T>
</nuxt-link>
</p>
<AdPlaceholder :phkey="['content-0', 'content-mobile-0']" />
<Spelling :text="content" />
<AdPlaceholder :phkey="['content-1', 'content-mobile-1']" />
<div class="d-print-none">
<Separator icon="heart" />
<BlogReactionSection :slug="slug" />
<section>
<Share :title="title ?? undefined" />
</section>
<Support />
</div>
</div>
</Page>
</template>
<style lang="scss">
@import "assets/variables";
.blog-post {
hyphens: auto;
text-align: justify;
h1 {
text-align: left;
}
img {
max-width: 100%;
}
figure {
width: 100%;
max-width: 24rem;
padding: $spacer;
img {
width: 100%;
}
figcaption {
margin-top: $spacer / 2;
font-size: $small-font-size;
}
}
.forms-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18rem, 3fr));
grid-gap: $spacer;
justify-items: center;
figure {
padding: 0;
figcaption {
font-size: 90%;
}
}
}
hr {
border: none;
border-top: 3px solid $primary;
opacity: 1;
}
}
.columnist-wall > .columnist-column {
transition: margin-top .2s ease-in-out;
}
.spoiler {
background-color: $dark;
color: $dark;
padding: map-get($spacers, 1);
border-radius: $border-radius;
cursor: pointer;
transition: background-color .2s ease-in-out;
&:hover {
background-color: $light;
}
}
</style>