Raw blog finished
This commit is contained in:
parent
44c60f7069
commit
eadc0b119d
|
@ -1 +1 @@
|
|||
[{"D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\transitions.scss":"1","D:\\Software\\Development\\Websites\\enderman.ch\\index\\app.vue":"2","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\EMail.vue":"3","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\about.vue":"4","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\index.vue":"5","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\projects.vue":"6","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\social.vue":"7","D:\\Software\\Development\\Websites\\enderman.ch\\index\\layouts\\Card.vue":"8","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Options.vue":"9","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Flooter.vue":"10","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\SwipeControls.vue":"11","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Logo.vue":"12","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Route.vue":"13","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\Portal.vue":"14","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\ui\\Icon.vue":"15","D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\vuetify.scss":"16","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\[...slug].vue":"17","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\index.vue":"18","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\[slug].vue":"19","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\content\\Card.vue":"20"},{"size":165,"mtime":1700688268778,"hashOfConfig":"21"},{"size":1550,"mtime":1700850776284,"hashOfConfig":"21"},{"size":804,"mtime":1700688952880,"hashOfConfig":"21"},{"size":2135,"mtime":1700689265787,"hashOfConfig":"21"},{"size":3117,"mtime":1700689271383,"hashOfConfig":"21"},{"size":2304,"mtime":1700689265769,"hashOfConfig":"21"},{"size":3232,"mtime":1700689265781,"hashOfConfig":"21"},{"size":1623,"mtime":1700748530633,"hashOfConfig":"21"},{"size":3065,"mtime":1708262194629,"hashOfConfig":"22"},{"size":1132,"mtime":1708254584095,"hashOfConfig":"22"},{"size":1269,"mtime":1708255068679,"hashOfConfig":"22"},{"size":1147,"mtime":1708248797872,"hashOfConfig":"22"},{"size":979,"mtime":1706003786966,"hashOfConfig":"22"},{"size":16968,"mtime":1708180277353,"hashOfConfig":"22"},{"size":935,"mtime":1706445231843,"hashOfConfig":"22"},{"size":120,"mtime":1706789613657,"hashOfConfig":"23"},{"size":1988,"mtime":1708191547889,"hashOfConfig":"22"},{"size":2560,"mtime":1708202402381,"hashOfConfig":"22"},{"size":983,"mtime":1708206997656,"hashOfConfig":"22"},{"size":1912,"mtime":1708290866348,"hashOfConfig":"22"},"5tgxr3","1nljphs","1lk8nat"]
|
||||
[{"D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\transitions.scss":"1","D:\\Software\\Development\\Websites\\enderman.ch\\index\\app.vue":"2","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\EMail.vue":"3","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\about.vue":"4","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\index.vue":"5","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\projects.vue":"6","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\social.vue":"7","D:\\Software\\Development\\Websites\\enderman.ch\\index\\layouts\\Card.vue":"8","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Options.vue":"9","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Flooter.vue":"10","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\SwipeControls.vue":"11","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Logo.vue":"12","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Route.vue":"13","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\Portal.vue":"14","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\ui\\Icon.vue":"15","D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\vuetify.scss":"16","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\[...slug].vue":"17","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\index.vue":"18","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\[slug].vue":"19","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\content\\Card.vue":"20","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\content\\OgImage.vue":"21","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\NotFound.vue":"22"},{"size":165,"mtime":1700688268778,"hashOfConfig":"23"},{"size":1550,"mtime":1700850776284,"hashOfConfig":"23"},{"size":804,"mtime":1700688952880,"hashOfConfig":"23"},{"size":2135,"mtime":1700689265787,"hashOfConfig":"23"},{"size":3117,"mtime":1700689271383,"hashOfConfig":"23"},{"size":2304,"mtime":1700689265769,"hashOfConfig":"23"},{"size":3232,"mtime":1700689265781,"hashOfConfig":"23"},{"size":1623,"mtime":1700748530633,"hashOfConfig":"23"},{"size":3065,"mtime":1708262194629,"hashOfConfig":"24"},{"size":1132,"mtime":1708254584095,"hashOfConfig":"24"},{"size":1269,"mtime":1708255068679,"hashOfConfig":"24"},{"size":1147,"mtime":1708248797872,"hashOfConfig":"24"},{"size":979,"mtime":1706003786966,"hashOfConfig":"24"},{"size":16968,"mtime":1708180277353,"hashOfConfig":"24"},{"size":935,"mtime":1706445231843,"hashOfConfig":"24"},{"size":120,"mtime":1706789613657,"hashOfConfig":"25"},{"size":2084,"mtime":1708424343371,"hashOfConfig":"24"},{"size":2560,"mtime":1708202402381,"hashOfConfig":"24"},{"size":983,"mtime":1708206997656,"hashOfConfig":"24"},{"size":1912,"mtime":1708290866348,"hashOfConfig":"24"},{"size":976,"mtime":1708431988520,"hashOfConfig":"24"},{"size":1645,"mtime":1708450985689,"hashOfConfig":"24"},"5tgxr3","1nljphs","1lk8nat"]
|
3
app.vue
3
app.vue
|
@ -8,9 +8,6 @@ const route = useRoute()
|
|||
useHead({
|
||||
titleTemplate: (chunk?) => {
|
||||
if (route.fullPath === '/') return config.title.full
|
||||
|
||||
console.log(route.fullPath.split('/'))
|
||||
|
||||
if (route.fullPath.split('/').length === 2)
|
||||
switch (route.fullPath.split('/')[1]) {
|
||||
case 'blog':
|
||||
|
|
|
@ -314,6 +314,11 @@ body {
|
|||
scroll-snap-type: both mandatory;
|
||||
scroll-snap-stop: normal;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
&-box {
|
||||
position: relative;
|
||||
|
||||
|
@ -397,19 +402,18 @@ body {
|
|||
filter: drop-shadow(1px 2px 3px rgb(0 0 0 / 100%));
|
||||
}
|
||||
|
||||
&-share {
|
||||
&-control {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&-control {
|
||||
&-share {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<script setup lang="ts">
|
||||
const config = useAppConfig()
|
||||
const meta = {
|
||||
title: 'Page not found',
|
||||
description:
|
||||
'The page you are looking for does not exist. It might have been removed, had its name changed, or is temporarily unavailable.',
|
||||
image: `${config.url}/images/logo.png`,
|
||||
url: config.url,
|
||||
}
|
||||
|
||||
let slug = useRoute().params.slug
|
||||
|
||||
if (Array.isArray(slug)) slug = slug.join('/')
|
||||
|
||||
const error = {
|
||||
http_code: 200,
|
||||
title: config.title,
|
||||
url: config.url + '/' + slug,
|
||||
locale: config.locale || 'en',
|
||||
data: {
|
||||
path: '/error.vue',
|
||||
content: {
|
||||
title: 'Page not found!',
|
||||
description:
|
||||
"The page you are looking for does not exist. However, no 404 error has occurred, as you're browsing through a web application loaded into memory and HTTP error codes make no sense here.",
|
||||
},
|
||||
},
|
||||
slug: useRoute().params.slug,
|
||||
build: config.build,
|
||||
hint: 'Use the navigation bar to get back to the main page.',
|
||||
}
|
||||
const errorString = JSON.stringify(error, null, 2)
|
||||
const buffer = ref('')
|
||||
|
||||
const startTime = Date.now()
|
||||
const dt = 20
|
||||
|
||||
let i = 0
|
||||
|
||||
function type() {
|
||||
if (Date.now() > startTime + dt * i) buffer.value += errorString[i++]
|
||||
if (i < errorString.length) requestAnimationFrame(type)
|
||||
}
|
||||
|
||||
onMounted(() => setTimeout(type, 1500))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<h3 class="alex">Page not found!</h3>
|
||||
<pre class="pre-wrap">{{ buffer }}<span class="blinker">▊</span></pre>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.blinker {
|
||||
animation: blinker 1s steps(2, start) infinite;
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
from {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,58 @@
|
|||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
logo: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sectionLogo: {
|
||||
type: null as unknown as PropType<string | null>,
|
||||
default: null,
|
||||
required: false,
|
||||
},
|
||||
thumbnail: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const sentence = computed(() => {
|
||||
return props.description.split('.')[0] + '...'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full flex flex-col justify-end text-white relative">
|
||||
<img
|
||||
:src="thumbnail"
|
||||
:alt="alt"
|
||||
class="w-full h-full object-cover absolute top-0 left-0"
|
||||
/>
|
||||
<div class="absolute top-0 left-0 p-5">
|
||||
<img :src="logo" alt="Logo" class="w-[100] h-[100]" />
|
||||
</div>
|
||||
<div v-if="sectionLogo" class="absolute top-0 right-0 p-5">
|
||||
<img :src="sectionLogo" alt="Logo" class="w-[100] h-[100]" />
|
||||
</div>
|
||||
|
||||
<div class="mb-5 px-5" style="text-shadow: black 1px 1px 10px">
|
||||
<h1 class="m-0 text-[60px]">{{ title }}</h1>
|
||||
<strong class="min-h-[90px] m-0 text-[24px]" style="font-family: Lato">{{
|
||||
sentence
|
||||
}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
title: Hello World!
|
||||
description: I created this post to test @nuxt/content.
|
||||
authors: ['Enderman']
|
||||
created: 2023-08-12T16:40:09Z
|
||||
updated: 2024-02-18T20:31:54Z
|
||||
draft: false
|
||||
tags: ['hello', 'world']
|
||||
thumbnail: '/images/blog/thumbnails/hello.jpg'
|
||||
---
|
||||
|
||||
# Hello!
|
||||
|
||||
This is an example piece of content
|
||||
This is an example piece of content.
|
||||
|
|
|
@ -6,11 +6,6 @@ created: 2023-08-13T15:51:19Z
|
|||
updated: 2024-02-18T21:32:12Z
|
||||
draft: false
|
||||
tags: ['windows', 'context menu', 'customization', 'registry', 'windows tweaks', 'shell', 'shell extensions', 'shell components', 'context menu handlers', 'windows 10', 'windows 11']
|
||||
thumbnail: /images/blog/thumbnails/the-windows-context-menu.png
|
||||
ogImage:
|
||||
component: BlogImage
|
||||
props:
|
||||
image: /images/blog/thumbnails/the-windows-context-menu.png
|
||||
---
|
||||
|
||||
# The Windows context menu is... poorly made.
|
||||
|
|
|
@ -44,7 +44,7 @@ const swipe = useSwipe(card, {
|
|||
class="dimensions background h-animated overflow-auto d-flex flex-column gap-3 gap-sm-2 px-4 pt-4 pb-3"
|
||||
:class="{
|
||||
'rounded-1-sm': !reader,
|
||||
'mh-0': reader,
|
||||
'mh-0 h-100': reader,
|
||||
}"
|
||||
>
|
||||
<Options />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
export default defineNuxtRouteMiddleware((to, _from) => {
|
||||
const { reader } = storeToRefs(usePageStore())
|
||||
|
||||
reader.value = /\/blog\/(.*)/.test(to.path)
|
||||
|
|
|
@ -34,6 +34,9 @@ export default defineNuxtConfig({
|
|||
features: {
|
||||
inlineStyles: false,
|
||||
},
|
||||
experimental: {
|
||||
inlineRouteRules: true,
|
||||
},
|
||||
components: {
|
||||
dirs: [
|
||||
{
|
||||
|
@ -59,7 +62,6 @@ export default defineNuxtConfig({
|
|||
'nuxt-link-checker',
|
||||
],
|
||||
content: {
|
||||
documentDriven: true,
|
||||
markdown: {
|
||||
remarkPlugins: ['remark-reading-time'],
|
||||
},
|
||||
|
@ -169,33 +171,34 @@ export default defineNuxtConfig({
|
|||
markdown: true,
|
||||
},
|
||||
},
|
||||
routeRules: {
|
||||
'/': {
|
||||
sitemap: {
|
||||
changefreq: 'yearly',
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
'/about': {
|
||||
sitemap: {
|
||||
changefreq: 'yearly',
|
||||
priority: 0.8,
|
||||
},
|
||||
},
|
||||
'/projects': {
|
||||
sitemap: {
|
||||
changefreq: 'monthly',
|
||||
priority: 0.8,
|
||||
},
|
||||
},
|
||||
'/social': {
|
||||
sitemap: {
|
||||
changefreq: 'yearly',
|
||||
priority: 0.8,
|
||||
},
|
||||
},
|
||||
},
|
||||
// routeRules: {
|
||||
// '/': {
|
||||
// sitemap: {
|
||||
// changefreq: 'yearly',
|
||||
// priority: 1,
|
||||
// },
|
||||
// },
|
||||
// '/about': {
|
||||
// sitemap: {
|
||||
// changefreq: 'yearly',
|
||||
// priority: 0.6,
|
||||
// },
|
||||
// },
|
||||
// '/projects': {
|
||||
// sitemap: {
|
||||
// changefreq: 'monthly',
|
||||
// priority: 0.7,
|
||||
// },
|
||||
// },
|
||||
// '/social': {
|
||||
// sitemap: {
|
||||
// changefreq: 'yearly',
|
||||
// priority: 0.7,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
sitemap: {
|
||||
sources: ['/api/__sitemap__/content'],
|
||||
cacheMaxAgeSeconds: 360,
|
||||
exclude: [],
|
||||
credits: false,
|
||||
|
@ -209,11 +212,11 @@ export default defineNuxtConfig({
|
|||
width: '12.5%',
|
||||
},
|
||||
],
|
||||
// defaults: { ← Defaults override route rules...
|
||||
// changefreq: 'monthly',
|
||||
// priority: 0.7,
|
||||
// lastmod: buildDate,
|
||||
// },
|
||||
defaults: {
|
||||
changefreq: 'yearly',
|
||||
priority: 0.7,
|
||||
lastmod: config.build.date,
|
||||
},
|
||||
},
|
||||
robots: {
|
||||
enabled: true,
|
||||
|
|
|
@ -35,58 +35,8 @@ useHead({
|
|||
},
|
||||
],
|
||||
})
|
||||
|
||||
const error = {
|
||||
http_code: 200,
|
||||
title: config.title,
|
||||
url: config.url,
|
||||
locale: config.locale || 'en',
|
||||
data: {
|
||||
path: '/error.vue',
|
||||
content: {
|
||||
title: 'Page not found!',
|
||||
description:
|
||||
"The page you are looking for does not exist. However, no 404 error has occurred, as you're browsing through a web application loaded into memory and HTTP error codes make no sense here.",
|
||||
},
|
||||
},
|
||||
slug: useRoute().params.slug,
|
||||
build: config.build,
|
||||
}
|
||||
const errorString = JSON.stringify(error, null, 2)
|
||||
const buffer = ref('')
|
||||
|
||||
const startTime = Date.now()
|
||||
const dt = 20
|
||||
|
||||
let i = 0
|
||||
|
||||
function type() {
|
||||
if (Date.now() > startTime + dt * i) buffer.value += errorString[i++]
|
||||
if (i < errorString.length) requestAnimationFrame(type)
|
||||
}
|
||||
|
||||
onMounted(() => setTimeout(type, 1500))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="alex">Page not found!</h3>
|
||||
<pre class="pre-wrap">{{ buffer }}<span class="blinker">▊</span></pre>
|
||||
</div>
|
||||
<NotFound />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.blinker {
|
||||
animation: blinker 1s steps(2, start) infinite;
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
from {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,18 +1,61 @@
|
|||
<script setup lang="ts">
|
||||
import { formatDate } from 'date-fns'
|
||||
const { slug } = useRoute().params
|
||||
import { useAsyncData } from '#app'
|
||||
|
||||
const config = useAppConfig()
|
||||
const content = useContent()
|
||||
|
||||
let thumbnail: string | null = null
|
||||
let slug = useRoute().params.slug
|
||||
|
||||
if (Array.isArray(slug)) slug = slug.join('/')
|
||||
|
||||
const { data } = await useAsyncData('home', () =>
|
||||
queryContent(`/blog/${slug}`).findOne(),
|
||||
)
|
||||
|
||||
const meta = {
|
||||
title: `${content.page.title}`,
|
||||
description: content.page.description,
|
||||
image: content.page.thumbnail,
|
||||
title: data.value ? data.value.title : 'Page not found',
|
||||
description: data.value
|
||||
? data.value.description
|
||||
: 'The page you are looking for does not exist. It might have been removed, had its name changed, or is temporarily unavailable.',
|
||||
url: `${config.url}/blog`,
|
||||
}
|
||||
|
||||
defineOgImage()
|
||||
// When defineOgImage is used, useSeoMeta must exclude ogImage and twitterImage properties.
|
||||
useSeoMeta({
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
ogTitle: meta.title,
|
||||
ogDescription: meta.description,
|
||||
// ogImage: EXCLUDED
|
||||
ogUrl: meta.url,
|
||||
ogType: 'article',
|
||||
twitterTitle: meta.title,
|
||||
twitterDescription: meta.description,
|
||||
// twitterImage: EXCLUDED
|
||||
twitterCard: 'summary_large_image',
|
||||
})
|
||||
|
||||
if (data.value) {
|
||||
thumbnail =
|
||||
data.value.thumbnail ??
|
||||
`/images/blog/thumbnails/${data.value._path!.split('/').at(-1)}.png`
|
||||
|
||||
// Generate the article's Open Graph image.
|
||||
defineOgImageComponent(
|
||||
'OgImage',
|
||||
{
|
||||
title: data.value.title,
|
||||
description: data.value.description,
|
||||
logo: '/images/logo.png',
|
||||
sectionLogo: '/images/chest.png',
|
||||
thumbnail,
|
||||
alt: data.value.title,
|
||||
},
|
||||
{
|
||||
fonts: ['Alexandria:700', 'Lato:700'],
|
||||
},
|
||||
)
|
||||
|
||||
// Hydrate the rendered items.
|
||||
onMounted(() => {
|
||||
|
@ -64,37 +107,51 @@ onUnmounted(() => {
|
|||
pre.querySelector('.clipboard')?.remove()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: 'Page not found',
|
||||
htmlAttrs: {
|
||||
lang: config.locale || 'en',
|
||||
},
|
||||
link: [
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
href: '/favicon.ico',
|
||||
},
|
||||
],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article
|
||||
v-if="data"
|
||||
class="post scrollbar fade-mask-sm d-flex flex-column gap-3 flex-grow-1 overflow-x-hidden overflow-y-auto py-sm-4 pe-sm-3"
|
||||
>
|
||||
<ContentDoc :path="`/blog/${slug}`">
|
||||
<template #default="{ doc }">
|
||||
<div
|
||||
class="post-preamble"
|
||||
:style="{ backgroundImage: 'url(' + doc.thumbnail + ')' }"
|
||||
:style="{ backgroundImage: 'url(' + thumbnail + ')' }"
|
||||
>
|
||||
<div class="p-2">
|
||||
<h3 class="alex">{{ doc.title }}</h3>
|
||||
<h3 class="alex">{{ data.title }}</h3>
|
||||
<div class="d-flex flex-row row-gap-0 column-gap-2 flex-wrap">
|
||||
<div class="d-flex flex-row gap-1 align-items-center">
|
||||
<iconify-icon icon="mdi:calendar" />
|
||||
<strong class="nobr font-small">
|
||||
{{ formatDate(doc.created, 'LLLL do, y – HH:mm') }}
|
||||
{{ formatDate(data.created, 'LLLL do, y – HH:mm') }}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="d-flex flex-row gap-1 align-items-center">
|
||||
<iconify-icon icon="mdi:clock-outline" />
|
||||
<strong class="nobr font-small">
|
||||
{{ doc.readingTime.text.split(' ')[0] + ' minutes to read' }}
|
||||
{{ data!.readingTime.text.split(' ')[0] + ' minutes to read' }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NuxtLink
|
||||
:href="`https://twitter.com/share?url=${config.url}/blog/${slug}&text=${doc.title}&hashtags=${doc.tags.slice(0, 3).join(',').replace(/ /g, '')}`"
|
||||
:href="`https://twitter.com/share?url=${config.url}/blog/${slug}&text=${data.title}&hashtags=${data.tags.slice(0, 3).join(',').replace(/ /g, '')}`"
|
||||
target="_blank"
|
||||
class="link post-preamble-share px-2 py-1"
|
||||
>
|
||||
|
@ -106,20 +163,10 @@ onUnmounted(() => {
|
|||
</div>
|
||||
<div class="post-content">
|
||||
<Separator class="mt-0" />
|
||||
<ContentRenderer :value="doc" />
|
||||
<ContentRenderer :value="data" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #not-found>
|
||||
<div
|
||||
class="d-flex flex-column justify-content-center align-items-center gap-3"
|
||||
>
|
||||
<span>Blog post not found 🙁</span>
|
||||
<NuxtLink to="/blog" class="link-hi">Back to blog</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
</ContentDoc>
|
||||
</article>
|
||||
<NotFound v-else />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
|
@ -41,7 +41,12 @@ useHead({
|
|||
|
||||
<template>
|
||||
<section>
|
||||
<h3 class="alex">Recent posts</h3>
|
||||
<h3 class="alex">The Enderchest</h3>
|
||||
<p>
|
||||
You're browsing the enderchest — a blog about software development,
|
||||
scientific research and Windows quirks.
|
||||
</p>
|
||||
<h4 class="alex">Recent posts</h4>
|
||||
<ContentList
|
||||
:query="{
|
||||
path: '/blog',
|
||||
|
@ -64,7 +69,10 @@ useHead({
|
|||
<div class="post-thumb">
|
||||
<img
|
||||
draggable="false"
|
||||
:src="post.thumbnail"
|
||||
:src="
|
||||
post.thumbnail ??
|
||||
`/images/blog/thumbnails/${post._path!.split('/').at(-1)}.png`
|
||||
"
|
||||
:alt="post.title"
|
||||
class="rounded-4"
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { defineRouteRules } from '#imports'
|
||||
|
||||
const config = useAppConfig()
|
||||
const meta = {
|
||||
title: 'Enderman',
|
||||
|
@ -34,6 +36,13 @@ useHead({
|
|||
},
|
||||
],
|
||||
})
|
||||
|
||||
defineRouteRules({
|
||||
sitemap: {
|
||||
changefreq: 'yearly',
|
||||
priority: 1,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 229 KiB |
Binary file not shown.
Before Width: | Height: | Size: 23 KiB |
|
@ -0,0 +1,16 @@
|
|||
import type { ParsedContent } from '@nuxt/content/dist/runtime/types'
|
||||
import { formatDate } from 'date-fns'
|
||||
|
||||
import { serverQueryContent } from '#content/server'
|
||||
|
||||
export default defineSitemapEventHandler(async (e) => {
|
||||
const contentList = (await serverQueryContent(e).find()) as ParsedContent[]
|
||||
return contentList.map((c) => {
|
||||
return asSitemapUrl({
|
||||
loc: c._path,
|
||||
lastmod: formatDate(c.updated, 'yyyy-MM-dd'),
|
||||
changefreq: 'monthly',
|
||||
priority: 0.9,
|
||||
})
|
||||
})
|
||||
})
|
|
@ -15,7 +15,7 @@
|
|||
"~/*": ["./*"],
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"types": ["@nuxt/types", "@nuxtjs/axios", "@nuxt/content", "@types/node"]
|
||||
"types": ["@nuxt/types", "@nuxtjs/axios", "@nuxt/content", "nuxt-simple-sitemap", "@types/node"]
|
||||
},
|
||||
"exclude": ["node_modules", ".nuxt", "dist"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue