189 lines
No EOL
7.5 KiB
Vue
189 lines
No EOL
7.5 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/pageupdater/update_pagelist';
|
|
import type { PageInfo, PageInfoMetdata } from '~/utils/pageupdater/pages';
|
|
|
|
// 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 article_contents: Ref<string> = ref("")
|
|
|
|
const metadata: Ref<PageInfoMetdata> = ref({
|
|
title: "",
|
|
description: "",
|
|
date: "",
|
|
tags: [],
|
|
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))
|
|
}
|
|
fetchList()
|
|
|
|
// Fetch the article contents from the URL
|
|
async function fetchArticle(url: string) {
|
|
const post = blog_list.posts.find(post => post.url === url)
|
|
if (post) {
|
|
const response = await fetch(post.url)
|
|
article_contents.value = await response.text()
|
|
}
|
|
}
|
|
|
|
function resetReadingPosition() {
|
|
window.scrollTo(0, 0)
|
|
}
|
|
|
|
function updateMetadata(meta: MarkdownMetadata) {
|
|
metadata.value = meta
|
|
}
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative z-50 flex w-full justify-center text-white">
|
|
<!-- Metadata -->
|
|
<MetaSet :title="metadata.title" :description="metadata.description" :date="metadata.date"
|
|
:background="metadata.background" :tags="metadata.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>{{ metadata.title }}</h1>
|
|
<small>{{ metadata.date ? new Date(metadata.date).toLocaleDateString() : "" }}</small>
|
|
<div class="max-w-50 flex flex-row justify-center">
|
|
<div v-for="tag in metadata.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="metadata.previous" :onclick="resetReadingPosition" :to="metadata.previous"
|
|
class="m-2 text-white">Previous</NuxtLink>
|
|
</div>
|
|
<div class="justify-end">
|
|
<NuxtLink v-if="metadata.next" :onclick="resetReadingPosition" :to="metadata.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>
|
|
<Markdown :input="article_contents" type="markdown" @metadata="updateMetadata" />
|
|
<!-- Aligned next/prev controls -->
|
|
<div class="flex">
|
|
<div class="justify-start">
|
|
<NuxtLink v-if="metadata.previous" :onclick="resetReadingPosition" :to="metadata.previous"
|
|
class="m-2 text-white">Previous</NuxtLink>
|
|
</div>
|
|
<div class="justify-end">
|
|
<NuxtLink v-if="metadata.next" :onclick="resetReadingPosition" :to="metadata.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> |