posts setup
This commit is contained in:
@@ -9,7 +9,7 @@ import { renderAsync } from "@resvg/resvg-js";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const __fonts = resolve(__dirname, "../fonts");
|
||||
|
||||
export async function generateImages(config: SiteConfig) {
|
||||
export async function generateImages(config: SiteConfig): Promise<void> {
|
||||
const pages = await createContentLoader("**/*.md", { excerpt: true }).load();
|
||||
const template = await readFile(resolve(__dirname, "./Template.vue"), "utf-8");
|
||||
|
||||
@@ -57,7 +57,12 @@ interface GenerateImagesOptions {
|
||||
fonts: SatoriOptions["fonts"];
|
||||
}
|
||||
|
||||
async function generateImage({ page, template, outDir, fonts }: GenerateImagesOptions) {
|
||||
async function generateImage({
|
||||
page,
|
||||
template,
|
||||
outDir,
|
||||
fonts,
|
||||
}: GenerateImagesOptions): Promise<void> {
|
||||
const { frontmatter, url } = page;
|
||||
|
||||
const options: SatoriOptions = {
|
||||
|
39
.vitepress/rss.ts
Normal file
39
.vitepress/rss.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import path from "path";
|
||||
import { writeFileSync } from "fs";
|
||||
import { Feed } from "feed";
|
||||
import { createContentLoader, type SiteConfig } from "vitepress";
|
||||
import { meta } from "./constants";
|
||||
|
||||
export async function genFeed(config: SiteConfig) {
|
||||
const feed = new Feed({
|
||||
title: "FMHY • Monthy Posts",
|
||||
description: meta.description,
|
||||
id: meta.hostname,
|
||||
link: meta.hostname,
|
||||
language: "en",
|
||||
image: "https://github.com/fmhy.png",
|
||||
copyright: "",
|
||||
});
|
||||
|
||||
const posts = await createContentLoader("posts/**/*.md", {
|
||||
excerpt: true,
|
||||
render: true,
|
||||
}).load();
|
||||
|
||||
posts.sort(
|
||||
(a, b) => +new Date(b.frontmatter.date as string) - +new Date(a.frontmatter.date as string),
|
||||
);
|
||||
|
||||
for (const { url, excerpt, frontmatter, html } of posts) {
|
||||
feed.addItem({
|
||||
title: frontmatter.title,
|
||||
id: `${meta.hostname}${url}`,
|
||||
link: `${meta.hostname}${url.split("/posts")[1]}`,
|
||||
description: excerpt,
|
||||
content: html,
|
||||
date: frontmatter.date,
|
||||
});
|
||||
}
|
||||
|
||||
writeFileSync(path.join(config.outDir, "feed.rss"), feed.rss2());
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
import Sidebar from "./components/SidebarCard.vue";
|
||||
import Announcement from "./components/Announcement.vue";
|
||||
import { useData } from "vitepress";
|
||||
import { nextTick, provide } from "vue";
|
||||
import Sidebar from "./components/SidebarCard.vue";
|
||||
import Announcement from "./components/Announcement.vue";
|
||||
|
||||
const { isDark } = useData();
|
||||
|
||||
|
27
.vitepress/theme/PostLayout.vue
Normal file
27
.vitepress/theme/PostLayout.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from "vitepress";
|
||||
import Authors from "./components/Authors.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
authors: string[];
|
||||
}>();
|
||||
|
||||
const formatDate = (raw: string): string => {
|
||||
const date = new Date(raw);
|
||||
return date.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const { frontmatter } = useData();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3>
|
||||
{{ frontmatter.title }}
|
||||
</h3>
|
||||
|
||||
<span>{{ frontmatter.description }} • {{ formatDate(frontmatter.date) }}</span>
|
||||
<Authors :authors="props.authors" />
|
||||
</template>
|
37
.vitepress/theme/Posts.vue
Normal file
37
.vitepress/theme/Posts.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<!-- eslint-disable vue/require-v-for-key -->
|
||||
<script setup lang="ts">
|
||||
import { data as posts } from "./posts.data";
|
||||
|
||||
const formatDate = (raw: string): string => {
|
||||
const date = new Date(raw);
|
||||
return date.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<section>
|
||||
<h1 class="flex items-center gap-2">Posts</h1>
|
||||
<p>Everything from Monthly Updates to fmhy updates.</p>
|
||||
</section>
|
||||
<template v-for="year in Object.keys(posts).reverse()" :key="year">
|
||||
<h2>{{ year }}</h2>
|
||||
<ul>
|
||||
<li v-for="post of posts[year]" :key="post.url">
|
||||
<article>
|
||||
<a :href="post.url" class="border-none">{{ post.title }}</a> -
|
||||
<dl class="m-0 inline">
|
||||
<dt class="sr-only">Published on</dt>
|
||||
<dd class="m-0 inline">
|
||||
<time :datetime="post.date" class="font-bold">{{ formatDate(post.date) }}</time>
|
||||
</dd>
|
||||
</dl>
|
||||
</article>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
43
.vitepress/theme/components/Authors.vue
Normal file
43
.vitepress/theme/components/Authors.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
authors: string[];
|
||||
}>();
|
||||
|
||||
interface Author {
|
||||
name: string;
|
||||
github: string;
|
||||
}
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: "nbats",
|
||||
github: "https://github.com/nbats",
|
||||
},
|
||||
{
|
||||
name: "Kai",
|
||||
github: "https://github.com/Kai-FMHY",
|
||||
},
|
||||
{
|
||||
name: "taskylizard",
|
||||
github: "https://github.com/taskylizard",
|
||||
},
|
||||
{
|
||||
name: "zinklog",
|
||||
github: "https://github.com/zinklog2",
|
||||
},
|
||||
] satisfies Author[];
|
||||
|
||||
const authors = computed(() => data.filter((author) => props.authors.includes(author.name)));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap gap-4 pt-2">
|
||||
<div v-for="(c, index) of authors" class="flex gap-2 items-center">
|
||||
<img :src="`${c.github}.png`" class="w-8 h-8 rounded-full" />
|
||||
<a :href="c.github">{{ c.name }}</a>
|
||||
<span v-if="index < authors.length - 1"> • </span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -1,6 +1,7 @@
|
||||
import { type Theme } from "vitepress";
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
import Layout from "./Layout.vue";
|
||||
import Post from "./PostLayout.vue";
|
||||
import { loadProgress } from "./composables/nprogress";
|
||||
import "./style.css";
|
||||
import "uno.css";
|
||||
@@ -8,7 +9,8 @@ import "uno.css";
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout,
|
||||
enhanceApp({ router }) {
|
||||
enhanceApp({ router, app }) {
|
||||
app.component("Post", Post);
|
||||
loadProgress(router);
|
||||
},
|
||||
} satisfies Theme;
|
||||
|
30
.vitepress/theme/posts.data.ts
Normal file
30
.vitepress/theme/posts.data.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createContentLoader, type ContentData } from "vitepress";
|
||||
import { groupBy } from "../utils";
|
||||
|
||||
interface Post {
|
||||
title: string;
|
||||
url: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
type Dictionary = ReturnType<typeof transformRawPosts>;
|
||||
|
||||
declare const data: Dictionary;
|
||||
export { data };
|
||||
|
||||
function transformRawPosts(rawPosts: ContentData[]): Record<string, Post[]> {
|
||||
const posts: Post[] = rawPosts
|
||||
.map(({ url, frontmatter }) => ({
|
||||
title: frontmatter.title,
|
||||
url,
|
||||
date: (frontmatter.date as Date).toISOString().slice(0, 10),
|
||||
}))
|
||||
.sort((a, b) => b.date.localeCompare(a.date));
|
||||
|
||||
return groupBy(posts, (post) => post.date.slice(0, 4));
|
||||
}
|
||||
|
||||
export default createContentLoader("posts/*.md", {
|
||||
includeSrc: true,
|
||||
transform: (raw) => transformRawPosts(raw),
|
||||
});
|
9
.vitepress/utils.ts
Normal file
9
.vitepress/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function groupBy<T, K extends keyof any>(arr: T[], key: (i: T) => K): Record<K, T[]> {
|
||||
return arr.reduce(
|
||||
(groups, item) => {
|
||||
(groups[key(item)] ||= []).push(item);
|
||||
return groups;
|
||||
},
|
||||
{} as Record<K, T[]>,
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user