personal-site/pages/blog.vue
2025-01-04 02:47:38 -08:00

215 lines
No EOL
8.2 KiB
Vue

<script setup lang="ts">
import { onMounted, watch, ref } from 'vue';
import fm from 'front-matter';
import PostCard from '../components/PostCard.vue';
import { globalMarkdown, MarkdownInput, type MarkdownMetadata } from '~/assets/markdown_conf';
import * as pages from '~/utils/page_updater/update_pagelist';
import type { PageInfo, PageInfoMetdata } from '~/utils/page_updater/pages';
import type { ParsedContent } from '@nuxt/content';
// Automatically maintained is a blog_list.json in assets/meta. This file contains a list of all blog posts and their metadata.
// This file is generated by a script in the utils/pageupdater folder.
const blog_list: pages.PageList = (await import('~/assets/meta/blog_list.json')) as pages.PageList;
let route = useRoute()
console.log(route)
const url: Ref<string> = ref("")
url.value = route.query.post as string
console.log(url.value)
const loading: Ref<boolean> = ref(false)
const list: Ref<any> = ref([])
const tagList: Ref<any> = ref([])
const tagFilter: Ref<string[]> = ref([])
tagFilter.value = []
const markdown: Ref<any> = ref(null)
const title: Ref<string> = ref("")
const description: Ref<string> = ref("")
const date: Ref<string> = ref("")
const tags: Ref<string[]> = ref([])
const background: Ref<string> = ref("")
const next: Ref<string> = ref("")
const previous: Ref<string> = ref("")
watch(markdown , (newVal) => {
if (newVal) {
title.value = newVal.title ? newVal.title : ""
description.value = newVal.description ? newVal.description : ""
date.value = newVal.date ? new Date(newVal.date).toLocaleDateString() : ""
tags.value = newVal.tags ? newVal.tags : []
background.value = newVal.background ? newVal.background : ""
}
})
// watch the params of the route to fetch the data again
watch(route, async () => {
url.value = route.query.post as string
if (url.value) {
console.log("Fetching article")
loading.value = true
try {
await fetchArticle(url.value)
}
finally {
loading.value = false
}
}
}, {
immediate: true
});
async function fetchList() {
// Extract the posts
list.value = blog_list.posts
// Extract the tags
tagList.value = blog_list.posts.flatMap(post => post.metadata.tags).filter((tag, index, self) => self.indexOf(tag) === index)
// Sort the posts by date, most recent first
list.value.sort((a: any, b: any) => b.metadata.date.localeCompare(a.metadata.date))
}
onMounted(async () => {
await fetchList()
await fetchArticle(url.value)
})
// Fetch the article contents from the URL
async function fetchArticle(url: string) {
const post = blog_list.posts.find(post => post.url === url)
if (post) {
// Trim the .md extension
var url = url.replace(/\.md$/, "")
console.log("Fetching article: " + url)
const { data } = await useAsyncData(url, () => queryContent(url).findOne())
console.log(data)
markdown.value = data.value;
}
}
function resetReadingPosition() {
window.scrollTo(0, 0)
}
fetchArticle(url.value)
console.log("Prefetching blog")
await fetchList();
const temp_url = route.query.post as string
await fetchArticle(temp_url);
</script>
<template>
<div class="relative z-50 flex w-full justify-center text-white">
<!-- Metadata -->
<MetaSet :title="title" :description="description" :date="date"
:background="background" :tags="tags" />
<!-- Main Content -->
<div class="mt-8 flex-col text-center">
<Transition name="list">
<!-- Article List -->
<div v-if="url == null" :key="url">
<h1>Blog</h1>
<div class="flex justify-center">
<div class="flex flex-wrap justify-center m-5 max-w-96">
<div v-for="tag in tagList" :key="tag" class="m-1">
<button
@click="tagFilter.includes(tag) ? tagFilter.splice(tagFilter.indexOf(tag), 1) : tagFilter.push(tag)"
class="text-xs bg-black border-purple-400 border text-white p-1 rounded-md"
:class="tagFilter.includes(tag) ? 'border-2 border-white bg-slate-700' : 'border-2 bg-black text-white'">{{
tag }}</button>
</div>
</div>
</div>
<div>
<TransitionGroup name="list">
<div
v-for="post in tagFilter.length == 0 ? list : list.filter((post: PageInfo) => post.metadata.tags ? post.metadata.tags.some(tag => tagFilter.includes(tag)) : false)">
<PostCard :url="post.url" key="{{post.id}}" :tagFilter="tagFilter" />
</div>
</TransitionGroup>
</div>
</div>
<!-- Article Viewer -->
<div v-else>
<Transition name="list">
<div class="flex flex-col" :key="url">
<h1>{{ title }}</h1>
<small>{{ date }}</small>
<div class="max-w-50 flex flex-row justify-center">
<div v-for="tag in tags" :key="tag" class="m-1 text-center">
<span
class="text-xs bg-black border-purple-400 border-2 text-white p-1 rounded-md">{{
tag }}</span>
</div>
</div>
<div>
<!-- Next/Prev controls, on the left and right side using PostCards -->
<div class="flex max-w-4xl max-md:w-screen">
<div class="justify-start">
<NuxtLink v-if="previous" :onclick="resetReadingPosition" :to="previous"
class="m-2 text-white">Previous</NuxtLink>
</div>
<div class="justify-end">
<NuxtLink v-if="next" :onclick="resetReadingPosition" :to="next"
class="m-2 text-white">Next</NuxtLink>
</div>
</div>
</div>
<!-- Article Content -->
<Card class="text-pretty max-w-4xl mt-4 max-md:w-screen text-left">
<article>
<div v-if="markdown != null">
<Markdown :input="markdown" />
</div>
<!-- Aligned next/prev controls -->
<div class="flex">
<div class="justify-start">
<NuxtLink v-if="previous" :onclick="resetReadingPosition" :to="previous"
class="m-2 text-white">Previous</NuxtLink>
</div>
<div class="justify-end">
<NuxtLink v-if="next" :onclick="resetReadingPosition" :to="next"
class="m-2 text-white">Next</NuxtLink>
</div>
</div>
</article>
</Card>
</div>
</Transition>
</div>
</Transition>
</div>
</div>
</template>
<style scoped>
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translate(100px, 0);
}
.list-leave-active {
position: absolute;
}
</style>