chore: upgrade to nuxt 4
This commit is contained in:
13
app/pages/404.vue
Normal file
13
app/pages/404.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
useTitle("404 - Not Found", "You're lost!");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="prose dark:prose-invert max-w-3xl transition">
|
||||
<h1 class="mb-1">404 - Not Found</h1>
|
||||
<p>
|
||||
You're lost! Don't worry, here's a link
|
||||
<a href="/">back to the home page.</a>
|
||||
</p>
|
||||
</main>
|
||||
</template>
|
78
app/pages/[...slug].vue
Normal file
78
app/pages/[...slug].vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import type { AnyParsedContent } from "@/shared/types";
|
||||
import { calcReadingTime } from "@/shared/metadata";
|
||||
|
||||
const route = useRoute();
|
||||
// definePageMeta({
|
||||
// layout: "withtop",
|
||||
// });
|
||||
|
||||
// we're not using ContentDoc because i need control
|
||||
const doc = await queryContent<AnyParsedContent>(route.path).findOne();
|
||||
const type = route.path.startsWith("/stories")
|
||||
? "stories"
|
||||
: route.path.startsWith("/blog")
|
||||
? "blog"
|
||||
: "unknown";
|
||||
|
||||
const descText =
|
||||
type === "stories"
|
||||
? `${calcReadingTime(doc).words.total} words`
|
||||
: `${calcReadingTime(doc).minutes} min read`;
|
||||
useTitle(doc.title, doc.description);
|
||||
|
||||
const captionText =
|
||||
type === "stories" ? "Story" : type === "blog" ? "Blog post" : "";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="container prose dark:prose-invert w-full">
|
||||
<p class="m-0 uppercase font-mono text-sm" v-if="captionText">
|
||||
{{ captionText }}
|
||||
</p>
|
||||
<h1 class="m-0">{{ doc.title }}</h1>
|
||||
<p class="my-2"><Date :doc="doc" /> · {{ descText }}</p>
|
||||
<div class="flex flex-wrap">
|
||||
<Tag
|
||||
v-for="(tag, index) in doc.tags"
|
||||
:dest="`/tags/${type}/${tag}`"
|
||||
:key="index"
|
||||
:name="tag"
|
||||
/>
|
||||
</div>
|
||||
<ContentRenderer :value="doc" tag="article" class="pt-0 w-full">
|
||||
<template #empty>
|
||||
<p>No description found.</p>
|
||||
</template>
|
||||
<template #not-found>
|
||||
<h1>404 - Not Found</h1>
|
||||
<p>
|
||||
Thanks for dropping by! But the page you're looking for can't be
|
||||
found.
|
||||
</p>
|
||||
</template>
|
||||
</ContentRenderer>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
width: 80%;
|
||||
max-width: 80ch;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.container {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.container h1 {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
</style>
|
42
app/pages/blog.vue
Normal file
42
app/pages/blog.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import type { BlogParsedContent } from "@/shared/types";
|
||||
|
||||
useTitle("Blog", "Ramblings and ideas");
|
||||
//definePageMeta({ layout: "withtop" });
|
||||
|
||||
// TODO: paginate stories
|
||||
const docs = await queryContent<BlogParsedContent>("/blog")
|
||||
.sort({ date: -1 })
|
||||
.where({ _draft: false })
|
||||
.find();
|
||||
|
||||
const tags = new Set(
|
||||
docs
|
||||
.map((p) => p.tags)
|
||||
.flat()
|
||||
.sort()
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
class="flex flex-col grow prose dark:prose-invert max-w-3xl gap-6 transition"
|
||||
>
|
||||
<h1 class="mb-0">Blog</h1>
|
||||
<div class="m-0">
|
||||
Filter:
|
||||
<Tag
|
||||
:dest="`/tags/blog/${tag}`"
|
||||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
:name="tag"
|
||||
/>
|
||||
</div>
|
||||
<PostPreviewCard
|
||||
v-for="(post, index) in docs"
|
||||
:key="index"
|
||||
:post
|
||||
type="blog"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
39
app/pages/index.vue
Normal file
39
app/pages/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import Services from "@/components/index/services.vue";
|
||||
import About from "@/components/index/about.vue";
|
||||
|
||||
//definePageMeta({ layout: "withtop" });
|
||||
useTitle("Home", "Personal website!");
|
||||
|
||||
const welcomeStrings = ["Welcome!", "Bienvenue!", "欢迎!"];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="flex flex-col items-center justify-around gap-8">
|
||||
<div class="flex flex-col items-center">
|
||||
<HeaderLoop class="text-bitter font-bold" :strings="welcomeStrings" />
|
||||
<p>What are you here to see?</p>
|
||||
<p>
|
||||
For my portfolio, please visit
|
||||
<a class="underline" href="https://github.com/potatoeggy">GitHub</a>
|
||||
instead.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-around items-start w-full flex-wrap gap-x-8 gap-y-10"
|
||||
>
|
||||
<BlogStatBox />
|
||||
<StoryStatBox />
|
||||
<CommitStatBox />
|
||||
</div>
|
||||
|
||||
<Services />
|
||||
<About />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
</style>
|
42
app/pages/stories.vue
Normal file
42
app/pages/stories.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import type { StoryParsedContent } from "@/shared/types";
|
||||
|
||||
useTitle("Stories", "Fantasies and worlds");
|
||||
//definePageMeta({ layout: "withtop" });
|
||||
|
||||
// TODO: paginate stories
|
||||
const docs = await queryContent<StoryParsedContent>("/stories")
|
||||
.sort({ date: -1 })
|
||||
.where({ _draft: false })
|
||||
.find();
|
||||
|
||||
const tags = new Set(
|
||||
docs
|
||||
.map((p) => p.tags)
|
||||
.flat()
|
||||
.sort()
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
class="flex flex-col grow prose dark:prose-invert max-w-3xl gap-6 transition"
|
||||
>
|
||||
<h1 class="mb-0">Stories</h1>
|
||||
<div class="m-0">
|
||||
Filter:
|
||||
<Tag
|
||||
:dest="`/tags/stories/${tag}`"
|
||||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
:name="tag"
|
||||
/>
|
||||
</div>
|
||||
<PostPreviewCard
|
||||
v-for="(story, index) in docs"
|
||||
:key="index"
|
||||
:post="story"
|
||||
type="stories"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
42
app/pages/tags/blog/[tag].vue
Normal file
42
app/pages/tags/blog/[tag].vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { tagInfo, type TagData } from "@/data/tagInfo";
|
||||
import type { BlogParsedContent } from "@/shared/types";
|
||||
|
||||
const route = useRoute();
|
||||
//definePageMeta({ layout: "withtop" });
|
||||
|
||||
const tag =
|
||||
typeof route.params.tag === "string" ? route.params.tag : route.params.tag[0];
|
||||
|
||||
const details: TagData = tagInfo[tag] ?? {};
|
||||
|
||||
const docs = await queryContent<BlogParsedContent>("/blog")
|
||||
.sort({ date: -1 })
|
||||
.where({ _draft: false, tags: { $contains: tag } })
|
||||
.find();
|
||||
|
||||
const title = details.name ?? `"${tag}"`;
|
||||
useTitle(title + " Posts", details.description);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
class="prose dark:prose-invert max-w-3xl flex flex-col grow gap-6 transition"
|
||||
>
|
||||
<div>
|
||||
<h1 class="mb-0">{{ title }} Posts</h1>
|
||||
<p
|
||||
v-if="details.description"
|
||||
v-html="details.description"
|
||||
class="mt-2"
|
||||
></p>
|
||||
</div>
|
||||
<PostPreviewCard
|
||||
v-for="(post, index) in docs"
|
||||
:key="index"
|
||||
:post
|
||||
:highlighttags="[tag]"
|
||||
type="blog"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
42
app/pages/tags/stories/[tag].vue
Normal file
42
app/pages/tags/stories/[tag].vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { tagInfo, type TagData } from "@/data/tagInfo";
|
||||
import type { StoryParsedContent } from "@/shared/types";
|
||||
|
||||
const route = useRoute();
|
||||
//definePageMeta({ layout: "withtop" });
|
||||
|
||||
const tag =
|
||||
typeof route.params.tag === "string" ? route.params.tag : route.params.tag[0];
|
||||
|
||||
const details: TagData = tagInfo[tag] ?? {};
|
||||
|
||||
const docs = await queryContent<StoryParsedContent>("/stories")
|
||||
.sort({ date: -1 })
|
||||
.where({ _draft: false, tags: { $contains: tag } })
|
||||
.find();
|
||||
|
||||
const title = details.name ?? `"${tag}"`;
|
||||
useTitle(title + " Stories", details.description);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
class="prose dark:prose-invert max-w-3xl flex flex-col grow gap-6 transition"
|
||||
>
|
||||
<div>
|
||||
<h1 class="mb-0">{{ title }} Stories</h1>
|
||||
<p
|
||||
v-if="details.description"
|
||||
v-html="details.description"
|
||||
class="mt-2"
|
||||
></p>
|
||||
</div>
|
||||
<PostPreviewCard
|
||||
v-for="(story, index) in docs"
|
||||
:key="index"
|
||||
:post="story"
|
||||
:highlighttags="[tag]"
|
||||
type="stories"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
Reference in New Issue
Block a user