Raw blog finished

This commit is contained in:
Andrew Illarionov 2024-02-20 21:15:29 +03:00
parent 44c60f7069
commit eadc0b119d
18 changed files with 356 additions and 200 deletions

View File

@ -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"]

View File

@ -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':

View File

@ -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;
}
}

View File

@ -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">&#9610;</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>

View File

@ -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>

View File

@ -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.

View File

@ -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.

View File

@ -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 />

View File

@ -1,4 +1,4 @@
export default defineNuxtRouteMiddleware((to, from) => {
export default defineNuxtRouteMiddleware((to, _from) => {
const { reader } = storeToRefs(usePageStore())
reader.value = /\/blog\/(.*)/.test(to.path)

View File

@ -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,

View File

@ -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">&#9610;</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>

View File

@ -1,125 +1,172 @@
<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()
// Hydrate the rendered items.
onMounted(() => {
document.querySelectorAll('pre').forEach((pre) => {
const icon = document.createElement('iconify-icon')
icon.setAttribute('icon', 'mdi:content-copy')
icon.setAttribute('inline', 'true')
icon.classList.add('button')
pre.appendChild(icon)
})
document
.querySelectorAll('code:not(pre *), pre > iconify-icon.button')
.forEach((code) => {
if (code instanceof HTMLElement) {
code.onclick = () => {
const area = document.createElement('textarea')
area.textContent =
code.nodeName && code.nodeName.toLowerCase() === 'code'
? code.textContent
: code.parentElement!.textContent
// It's necessary to create the textarea element every time you copy to get access to the select() method.
area.setSelectionRange(0, 99999) // An iOS gotcha.
area.select()
// Copy the text inside the textarea.
navigator.clipboard.writeText(area.value)
// TODO: Alert the user text has been successfully copied.
// Remove the textarea element.
area.remove()
}
}
})
// 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',
})
onUnmounted(() => {
document.querySelectorAll('code').forEach((code) => {
code.onclick = null
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(() => {
document.querySelectorAll('pre').forEach((pre) => {
const icon = document.createElement('iconify-icon')
icon.setAttribute('icon', 'mdi:content-copy')
icon.setAttribute('inline', 'true')
icon.classList.add('button')
pre.appendChild(icon)
})
document
.querySelectorAll('code:not(pre *), pre > iconify-icon.button')
.forEach((code) => {
if (code instanceof HTMLElement) {
code.onclick = () => {
const area = document.createElement('textarea')
area.textContent =
code.nodeName && code.nodeName.toLowerCase() === 'code'
? code.textContent
: code.parentElement!.textContent
// It's necessary to create the textarea element every time you copy to get access to the select() method.
area.setSelectionRange(0, 99999) // An iOS gotcha.
area.select()
// Copy the text inside the textarea.
navigator.clipboard.writeText(area.value)
// TODO: Alert the user text has been successfully copied.
// Remove the textarea element.
area.remove()
}
}
})
})
document.querySelectorAll('pre').forEach((pre) => {
pre.querySelector('.clipboard')?.remove()
onUnmounted(() => {
document.querySelectorAll('code').forEach((code) => {
code.onclick = null
})
document.querySelectorAll('pre').forEach((pre) => {
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 + ')' }"
>
<div class="p-2">
<h3 class="alex">{{ doc.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 &ndash; 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' }}
</strong>
</div>
</div>
<div
class="post-preamble"
:style="{ backgroundImage: 'url(' + thumbnail + ')' }"
>
<div class="p-2">
<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(data.created, 'LLLL do, y &ndash; 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">
{{ data!.readingTime.text.split(' ')[0] + ' minutes to read' }}
</strong>
</div>
<NuxtLink
:href="`https://twitter.com/share?url=${config.url}/blog/${slug}&text=${doc.title}&hashtags=${doc.tags.slice(0, 3).join(',').replace(/ /g, '')}`"
target="_blank"
class="link post-preamble-share px-2 py-1"
>
<iconify-icon icon="mdi:twitter"></iconify-icon>
</NuxtLink>
<NuxtLink to="/blog" class="link post-preamble-control px-2 py-1">
<iconify-icon icon="icon-park-solid:back"></iconify-icon>
</NuxtLink>
</div>
<div class="post-content">
<Separator class="mt-0" />
<ContentRenderer :value="doc" />
</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>
</div>
<NuxtLink
: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"
>
<iconify-icon icon="mdi:twitter"></iconify-icon>
</NuxtLink>
<NuxtLink to="/blog" class="link post-preamble-control px-2 py-1">
<iconify-icon icon="icon-park-solid:back"></iconify-icon>
</NuxtLink>
</div>
<div class="post-content">
<Separator class="mt-0" />
<ContentRenderer :value="data" />
</div>
</article>
<NotFound v-else />
</template>
<style scoped lang="scss"></style>

View File

@ -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"
/>

View File

@ -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

View File

@ -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,
})
})
})

View File

@ -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"]
}