202 lines
No EOL
6.2 KiB
Vue
202 lines
No EOL
6.2 KiB
Vue
<script setup>
|
|
import { onMounted, watch, ref } from 'vue';
|
|
import fm from 'front-matter';
|
|
import markdownit from 'markdown-it'
|
|
import PostCard from '../components/PostCard.vue';
|
|
import hljs from 'highlight.js';
|
|
|
|
// Automatically maintained is a blog_list.json in assets/. This file contains a list of all blog posts and their metadata.
|
|
// This file is generated by a script in the root directory of the project.
|
|
import blog_list from '../assets/blog_list.json';
|
|
|
|
const md = markdownit({
|
|
breaks: true,
|
|
typographer: true,
|
|
html: true,
|
|
highlight: function (str, lang) {
|
|
if (lang && hljs.getLanguage(lang)) {
|
|
try {
|
|
return '<pre><code class="hljs">' +
|
|
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
|
|
'</code></pre>';
|
|
} catch (__) { }
|
|
}
|
|
|
|
return '<pre><code class="hljs">' + md.utils.escapeHtml(str) + '</code></pre>';
|
|
}
|
|
})
|
|
|
|
|
|
md.renderer.rules.hr = function (tokens, idx, options, env, self) {
|
|
return '<div class="rounded-full h-0.5 bg-purple-500 my-3"></div>'
|
|
}
|
|
|
|
md.renderer.rules.softbreak = function (tokens, idx, options, env, self) {
|
|
return '<br>'
|
|
}
|
|
|
|
md.renderer.rules.hardbreak = function (tokens, idx, options, env, self) {
|
|
return '<br><br>'
|
|
}
|
|
|
|
const modules = import.meta.glob('/blog/');
|
|
|
|
for (const path in modules) {
|
|
const module = await modules[path]();
|
|
console.log(path, module.default); // module.default contains the image data
|
|
}
|
|
|
|
let route = useRoute();
|
|
console.log(route)
|
|
|
|
|
|
const url = ref(null)
|
|
url.value = route.query.post
|
|
console.log(url.value)
|
|
|
|
const loading = ref(false)
|
|
const data = ref([])
|
|
const text = ref([])
|
|
const error = ref([])
|
|
|
|
const list = ref([])
|
|
const tagList = ref([])
|
|
const tagFilter = ref([])
|
|
tagFilter.value = []
|
|
|
|
|
|
const background = ref('');
|
|
const title = ref(null);
|
|
const description = ref(null);
|
|
const date = ref(null);
|
|
const tags = ref(null);
|
|
|
|
// watch the params of the route to fetch the data again
|
|
|
|
fetchData(url.value)
|
|
|
|
|
|
async function fetchData(url) {
|
|
error.value = data.value = null
|
|
loading.value = true
|
|
console.log(url)
|
|
try {
|
|
data.value = await $fetch(url)
|
|
console.log(data.value)
|
|
const processed = fm(data.value)
|
|
text.value = md.render(processed.body)
|
|
console.log(text.value)
|
|
background.value = processed.attributes.background
|
|
title.value = processed.attributes.title
|
|
description.value = processed.attributes.description
|
|
date.value = processed.attributes.date
|
|
tags.value = processed.attributes.tags
|
|
} catch (err) {
|
|
error.value = err.toString()
|
|
console.error(err)
|
|
loading.value = false
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
/* If tags are specified, include only posts with those tags */
|
|
/* else, include all posts */
|
|
async function fetchList() {
|
|
/*Example formatting:
|
|
{
|
|
"posts": [
|
|
{
|
|
"metadata": {
|
|
"test": "mrrp"
|
|
},
|
|
"id": "test",
|
|
"url": "/blog/test"
|
|
},
|
|
{
|
|
"metadata": {
|
|
" title": "Awesome", "description": "A curated list of awesome stuff I like", "date": "2024-11-26", "tags": ["awesome", "curated"]
|
|
},
|
|
"id": "awesome",
|
|
"url": "/blog/awesome"
|
|
},
|
|
{
|
|
"metadata": {
|
|
"test": "mrrp",
|
|
"test2": "nya"
|
|
},
|
|
"id": "test2",
|
|
"url": "/blog/test2"
|
|
}
|
|
]
|
|
}
|
|
*/
|
|
|
|
// 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)
|
|
|
|
}
|
|
fetchList()
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative z-50 flex w-full justify-center text-white">
|
|
<div class="mt-8 flex-col text-center">
|
|
<div v-if="url == null">
|
|
<h1>Blog</h1>
|
|
<div class="flex justify-center m-5">
|
|
<div v-for="tag in tagList" :key="tag" class="m-1 text-center">
|
|
<button
|
|
@click="tagFilter.includes(tag) ? tagFilter.splice(tagFilter.indexOf(tag), 1) : tagFilter.push(tag)"
|
|
class="text-xs bg-purple-800 border-purple-400 border text-white p-1 rounded-md"
|
|
:class="tagFilter.includes(tag) ? 'border-2 border-purple-200 bg-purple-500' : 'border-1 border-purple-600 bg-purple-800 text-white'">{{
|
|
tag }}</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<TransitionGroup name="list">
|
|
<div v-for="post in tagFilter.length == 0 ? list : list.filter(post => tagFilter.some(tag => post.metadata.tags.includes(tag)))" :key="post.id">
|
|
<PostCard :url="post.url" :key="post.id" />
|
|
</div>
|
|
</TransitionGroup>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<div class="flex flex-col">
|
|
<h1>{{ title }}</h1>
|
|
<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-purple-800 border-purple-400 border text-white p-1 rounded-md">{{
|
|
tag }}</span>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="text-pretty min-w-96 text-left max-w-4xl mt-4 p-6 max-md:w-screen rounded-md container bg-opacity-90 bg-purple-950">
|
|
<div v-html="text"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.list-move,
|
|
.list-enter-active,
|
|
.list-leave-active {
|
|
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
|
|
}
|
|
|
|
.list-enter-from,
|
|
.list-leave-to {
|
|
opacity: 0;
|
|
transform: translate(100px, 0);
|
|
}
|
|
|
|
.list-leave-active {
|
|
position: absolute;
|
|
}
|
|
</style> |