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({
|
useHead({
|
||||||
titleTemplate: (chunk?) => {
|
titleTemplate: (chunk?) => {
|
||||||
if (route.fullPath === '/') return config.title.full
|
if (route.fullPath === '/') return config.title.full
|
||||||
|
|
||||||
console.log(route.fullPath.split('/'))
|
|
||||||
|
|
||||||
if (route.fullPath.split('/').length === 2)
|
if (route.fullPath.split('/').length === 2)
|
||||||
switch (route.fullPath.split('/')[1]) {
|
switch (route.fullPath.split('/')[1]) {
|
||||||
case 'blog':
|
case 'blog':
|
||||||
|
|
|
@ -314,6 +314,11 @@ body {
|
||||||
scroll-snap-type: both mandatory;
|
scroll-snap-type: both mandatory;
|
||||||
scroll-snap-stop: normal;
|
scroll-snap-stop: normal;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
scroll-snap-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
&-box {
|
&-box {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -397,19 +402,18 @@ body {
|
||||||
filter: drop-shadow(1px 2px 3px rgb(0 0 0 / 100%));
|
filter: drop-shadow(1px 2px 3px rgb(0 0 0 / 100%));
|
||||||
}
|
}
|
||||||
|
|
||||||
&-share {
|
&-control {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-control {
|
&-share {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 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!
|
title: Hello World!
|
||||||
description: I created this post to test @nuxt/content.
|
description: I created this post to test @nuxt/content.
|
||||||
|
authors: ['Enderman']
|
||||||
created: 2023-08-12T16:40:09Z
|
created: 2023-08-12T16:40:09Z
|
||||||
updated: 2024-02-18T20:31:54Z
|
updated: 2024-02-18T20:31:54Z
|
||||||
draft: false
|
draft: false
|
||||||
tags: ['hello', 'world']
|
tags: ['hello', 'world']
|
||||||
thumbnail: '/images/blog/thumbnails/hello.jpg'
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Hello!
|
# 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
|
updated: 2024-02-18T21:32:12Z
|
||||||
draft: false
|
draft: false
|
||||||
tags: ['windows', 'context menu', 'customization', 'registry', 'windows tweaks', 'shell', 'shell extensions', 'shell components', 'context menu handlers', 'windows 10', 'windows 11']
|
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.
|
# 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="dimensions background h-animated overflow-auto d-flex flex-column gap-3 gap-sm-2 px-4 pt-4 pb-3"
|
||||||
:class="{
|
:class="{
|
||||||
'rounded-1-sm': !reader,
|
'rounded-1-sm': !reader,
|
||||||
'mh-0': reader,
|
'mh-0 h-100': reader,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<Options />
|
<Options />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default defineNuxtRouteMiddleware((to, from) => {
|
export default defineNuxtRouteMiddleware((to, _from) => {
|
||||||
const { reader } = storeToRefs(usePageStore())
|
const { reader } = storeToRefs(usePageStore())
|
||||||
|
|
||||||
reader.value = /\/blog\/(.*)/.test(to.path)
|
reader.value = /\/blog\/(.*)/.test(to.path)
|
||||||
|
|
|
@ -34,6 +34,9 @@ export default defineNuxtConfig({
|
||||||
features: {
|
features: {
|
||||||
inlineStyles: false,
|
inlineStyles: false,
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
inlineRouteRules: true,
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
dirs: [
|
dirs: [
|
||||||
{
|
{
|
||||||
|
@ -59,7 +62,6 @@ export default defineNuxtConfig({
|
||||||
'nuxt-link-checker',
|
'nuxt-link-checker',
|
||||||
],
|
],
|
||||||
content: {
|
content: {
|
||||||
documentDriven: true,
|
|
||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: ['remark-reading-time'],
|
remarkPlugins: ['remark-reading-time'],
|
||||||
},
|
},
|
||||||
|
@ -169,33 +171,34 @@ export default defineNuxtConfig({
|
||||||
markdown: true,
|
markdown: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
routeRules: {
|
// routeRules: {
|
||||||
'/': {
|
// '/': {
|
||||||
sitemap: {
|
// sitemap: {
|
||||||
changefreq: 'yearly',
|
// changefreq: 'yearly',
|
||||||
priority: 1,
|
// priority: 1,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
'/about': {
|
// '/about': {
|
||||||
sitemap: {
|
// sitemap: {
|
||||||
changefreq: 'yearly',
|
// changefreq: 'yearly',
|
||||||
priority: 0.8,
|
// priority: 0.6,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
'/projects': {
|
// '/projects': {
|
||||||
sitemap: {
|
// sitemap: {
|
||||||
changefreq: 'monthly',
|
// changefreq: 'monthly',
|
||||||
priority: 0.8,
|
// priority: 0.7,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
'/social': {
|
// '/social': {
|
||||||
sitemap: {
|
// sitemap: {
|
||||||
changefreq: 'yearly',
|
// changefreq: 'yearly',
|
||||||
priority: 0.8,
|
// priority: 0.7,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
sitemap: {
|
sitemap: {
|
||||||
|
sources: ['/api/__sitemap__/content'],
|
||||||
cacheMaxAgeSeconds: 360,
|
cacheMaxAgeSeconds: 360,
|
||||||
exclude: [],
|
exclude: [],
|
||||||
credits: false,
|
credits: false,
|
||||||
|
@ -209,11 +212,11 @@ export default defineNuxtConfig({
|
||||||
width: '12.5%',
|
width: '12.5%',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// defaults: { ← Defaults override route rules...
|
defaults: {
|
||||||
// changefreq: 'monthly',
|
changefreq: 'yearly',
|
||||||
// priority: 0.7,
|
priority: 0.7,
|
||||||
// lastmod: buildDate,
|
lastmod: config.build.date,
|
||||||
// },
|
},
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
enabled: true,
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<NotFound />
|
||||||
<h3 class="alex">Page not found!</h3>
|
|
||||||
<pre class="pre-wrap">{{ buffer }}<span class="blinker">▊</span></pre>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.blinker {
|
|
||||||
animation: blinker 1s steps(2, start) infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes blinker {
|
|
||||||
from {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,21 +1,64 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { formatDate } from 'date-fns'
|
import { formatDate } from 'date-fns'
|
||||||
const { slug } = useRoute().params
|
import { useAsyncData } from '#app'
|
||||||
|
|
||||||
const config = useAppConfig()
|
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 = {
|
const meta = {
|
||||||
title: `${content.page.title}`,
|
title: data.value ? data.value.title : 'Page not found',
|
||||||
description: content.page.description,
|
description: data.value
|
||||||
image: content.page.thumbnail,
|
? 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`,
|
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',
|
||||||
|
})
|
||||||
|
|
||||||
// Hydrate the rendered items.
|
if (data.value) {
|
||||||
onMounted(() => {
|
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(() => {
|
||||||
document.querySelectorAll('pre').forEach((pre) => {
|
document.querySelectorAll('pre').forEach((pre) => {
|
||||||
const icon = document.createElement('iconify-icon')
|
const icon = document.createElement('iconify-icon')
|
||||||
|
|
||||||
|
@ -53,9 +96,9 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.querySelectorAll('code').forEach((code) => {
|
document.querySelectorAll('code').forEach((code) => {
|
||||||
code.onclick = null
|
code.onclick = null
|
||||||
})
|
})
|
||||||
|
@ -63,38 +106,52 @@ onUnmounted(() => {
|
||||||
document.querySelectorAll('pre').forEach((pre) => {
|
document.querySelectorAll('pre').forEach((pre) => {
|
||||||
pre.querySelector('.clipboard')?.remove()
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<article
|
<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"
|
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
|
<div
|
||||||
class="post-preamble"
|
class="post-preamble"
|
||||||
:style="{ backgroundImage: 'url(' + doc.thumbnail + ')' }"
|
:style="{ backgroundImage: 'url(' + thumbnail + ')' }"
|
||||||
>
|
>
|
||||||
<div class="p-2">
|
<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 row-gap-0 column-gap-2 flex-wrap">
|
||||||
<div class="d-flex flex-row gap-1 align-items-center">
|
<div class="d-flex flex-row gap-1 align-items-center">
|
||||||
<iconify-icon icon="mdi:calendar" />
|
<iconify-icon icon="mdi:calendar" />
|
||||||
<strong class="nobr font-small">
|
<strong class="nobr font-small">
|
||||||
{{ formatDate(doc.created, 'LLLL do, y – HH:mm') }}
|
{{ formatDate(data.created, 'LLLL do, y – HH:mm') }}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-row gap-1 align-items-center">
|
<div class="d-flex flex-row gap-1 align-items-center">
|
||||||
<iconify-icon icon="mdi:clock-outline" />
|
<iconify-icon icon="mdi:clock-outline" />
|
||||||
<strong class="nobr font-small">
|
<strong class="nobr font-small">
|
||||||
{{ doc.readingTime.text.split(' ')[0] + ' minutes to read' }}
|
{{ data!.readingTime.text.split(' ')[0] + ' minutes to read' }}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NuxtLink
|
<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"
|
target="_blank"
|
||||||
class="link post-preamble-share px-2 py-1"
|
class="link post-preamble-share px-2 py-1"
|
||||||
>
|
>
|
||||||
|
@ -106,20 +163,10 @@ onUnmounted(() => {
|
||||||
</div>
|
</div>
|
||||||
<div class="post-content">
|
<div class="post-content">
|
||||||
<Separator class="mt-0" />
|
<Separator class="mt-0" />
|
||||||
<ContentRenderer :value="doc" />
|
<ContentRenderer :value="data" />
|
||||||
</div>
|
</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>
|
</article>
|
||||||
|
<NotFound v-else />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|
|
@ -41,7 +41,12 @@ useHead({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<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
|
<ContentList
|
||||||
:query="{
|
:query="{
|
||||||
path: '/blog',
|
path: '/blog',
|
||||||
|
@ -64,7 +69,10 @@ useHead({
|
||||||
<div class="post-thumb">
|
<div class="post-thumb">
|
||||||
<img
|
<img
|
||||||
draggable="false"
|
draggable="false"
|
||||||
:src="post.thumbnail"
|
:src="
|
||||||
|
post.thumbnail ??
|
||||||
|
`/images/blog/thumbnails/${post._path!.split('/').at(-1)}.png`
|
||||||
|
"
|
||||||
:alt="post.title"
|
:alt="post.title"
|
||||||
class="rounded-4"
|
class="rounded-4"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { defineRouteRules } from '#imports'
|
||||||
|
|
||||||
const config = useAppConfig()
|
const config = useAppConfig()
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Enderman',
|
title: 'Enderman',
|
||||||
|
@ -34,6 +36,13 @@ useHead({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineRouteRules({
|
||||||
|
sitemap: {
|
||||||
|
changefreq: 'yearly',
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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"]
|
"exclude": ["node_modules", ".nuxt", "dist"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue