:3
This commit is contained in:
parent
c7f546f58a
commit
b62a50b12e
12 changed files with 444 additions and 638 deletions
4
.github/workflows/nuxtjs.yml
vendored
4
.github/workflows/nuxtjs.yml
vendored
|
@ -42,8 +42,8 @@ jobs:
|
||||||
run: pip install python-frontmatter && deno install
|
run: pip install python-frontmatter && deno install
|
||||||
- name: Update Page list
|
- name: Update Page list
|
||||||
run: deno --allow-all utils/page_updater/update_pagelist.ts
|
run: deno --allow-all utils/page_updater/update_pagelist.ts
|
||||||
- name: Update Page history
|
# - name: Update Page history
|
||||||
run: python utils/page_updater/commit_post_history.py
|
# run: python utils/page_updater/commit_post_history.py
|
||||||
# - name: Generate RSS feed
|
# - name: Generate RSS feed
|
||||||
# run: python utils/page_updater/rss_xml_gen.py
|
# run: python utils/page_updater/rss_xml_gen.py
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
|
|
|
@ -1,275 +0,0 @@
|
||||||
// TypeScript utilities for rendering Markdown/HTML content
|
|
||||||
|
|
||||||
import hljs from "highlight.js";
|
|
||||||
import MarkdownIt from "markdown-it";
|
|
||||||
import { alert } from "@mdit/plugin-alert";
|
|
||||||
import { tab } from "@mdit/plugin-tab";
|
|
||||||
import { tasklist } from "@mdit/plugin-tasklist";
|
|
||||||
import { mark } from "@mdit/plugin-mark";
|
|
||||||
import { footnote } from "@mdit/plugin-footnote";
|
|
||||||
import fm, { type FrontMatterResult } from "front-matter";
|
|
||||||
|
|
||||||
export interface MarkdownMetadata {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
date: string;
|
|
||||||
tags: string[];
|
|
||||||
background: string;
|
|
||||||
next?: string;
|
|
||||||
previous?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MarkdownOutput {
|
|
||||||
metadata: MarkdownMetadata;
|
|
||||||
contents: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isolate_markdown(input: string): MarkdownOutput {
|
|
||||||
const front_matter: FrontMatterResult<any> = fm(input);
|
|
||||||
const content = front_matter.body;
|
|
||||||
|
|
||||||
const metadata: MarkdownMetadata = {
|
|
||||||
title: front_matter.attributes.title || "",
|
|
||||||
description: front_matter.attributes.description || "",
|
|
||||||
date: front_matter.attributes.date || "",
|
|
||||||
tags: front_matter.attributes.tags || [],
|
|
||||||
background: front_matter.attributes.background || "",
|
|
||||||
next: front_matter.attributes.next || "",
|
|
||||||
previous: front_matter.attributes.previous || ""
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
metadata: metadata,
|
|
||||||
contents: content
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isolate_html(input: string): MarkdownOutput {
|
|
||||||
const title = input.match(/<title>([^<]+)<\/title>/);
|
|
||||||
const meta = input.match(/<meta name="([^"]+)" content="([^"]+)">/g);
|
|
||||||
const metadata: MarkdownMetadata = {
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
date: "",
|
|
||||||
tags: [],
|
|
||||||
background: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
if (meta) {
|
|
||||||
|
|
||||||
for (const tag of meta) {
|
|
||||||
const match = tag.match(/<meta name="([^"]+)" content="([^"]+)">/);
|
|
||||||
if (match) {
|
|
||||||
switch (match[1]) {
|
|
||||||
case "title":
|
|
||||||
metadata.title = match[2];
|
|
||||||
break;
|
|
||||||
case "description":
|
|
||||||
metadata.description = match[2];
|
|
||||||
break;
|
|
||||||
case "date":
|
|
||||||
metadata.date = match[2];
|
|
||||||
break;
|
|
||||||
case "tags":
|
|
||||||
metadata.tags = match[2].split(",");
|
|
||||||
break;
|
|
||||||
case "background":
|
|
||||||
metadata.background = match[2];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const front_matter: FrontMatterResult<any> = fm(input);
|
|
||||||
|
|
||||||
if (front_matter.attributes) {
|
|
||||||
metadata.title = front_matter.attributes.title || metadata.title;
|
|
||||||
metadata.description = front_matter.attributes.description || metadata.description;
|
|
||||||
metadata.date = front_matter.attributes.date || metadata.date;
|
|
||||||
metadata.tags = front_matter.attributes.tags || metadata.tags;
|
|
||||||
metadata.background = front_matter.attributes.background || metadata.background;
|
|
||||||
metadata.next = front_matter.attributes.next || "";
|
|
||||||
metadata.previous = front_matter.attributes.previous || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
metadata: metadata,
|
|
||||||
contents: input
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MarkdownInput {
|
|
||||||
type: "markdown" | "html";
|
|
||||||
private contents: string;
|
|
||||||
private metadata: MarkdownMetadata;
|
|
||||||
|
|
||||||
constructor(type: "markdown" | "html", data: string) {
|
|
||||||
this.type = type;
|
|
||||||
this.metadata = this.get_metadata();
|
|
||||||
|
|
||||||
switch (this.type) {
|
|
||||||
case "markdown":
|
|
||||||
const result = isolate_markdown(data);
|
|
||||||
this.contents = result.contents;
|
|
||||||
this.metadata = result.metadata;
|
|
||||||
break;
|
|
||||||
case "html":
|
|
||||||
const result_html = isolate_html(data);
|
|
||||||
this.contents = data;
|
|
||||||
this.metadata = result_html.metadata;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static from_markdown(data: string): MarkdownInput {
|
|
||||||
return new MarkdownInput("markdown", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static from_html(data: string): MarkdownInput {
|
|
||||||
return new MarkdownInput("html", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async from_url(url: string): Promise<MarkdownInput> {
|
|
||||||
const response = await fetch(url);
|
|
||||||
const data = await response.text();
|
|
||||||
|
|
||||||
// Check the content type of the response
|
|
||||||
const content_type = response.headers.get("content-type");
|
|
||||||
if (content_type) {
|
|
||||||
if (content_type.includes("text/markdown")) {
|
|
||||||
return MarkdownInput.from_markdown(data);
|
|
||||||
} else if (content_type.includes("text/html")) {
|
|
||||||
return MarkdownInput.from_html(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.endsWith(".md")) {
|
|
||||||
return MarkdownInput.from_markdown(data);
|
|
||||||
} else if (url.endsWith(".html")) {
|
|
||||||
return MarkdownInput.from_html(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to markdown
|
|
||||||
return MarkdownInput.from_markdown(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get_contents(): string {
|
|
||||||
return this.contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get_metadata(): MarkdownMetadata {
|
|
||||||
return this.metadata;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MarkdownContext {
|
|
||||||
private md: MarkdownIt;
|
|
||||||
|
|
||||||
constructor(md: MarkdownIt | undefined) {
|
|
||||||
if (md) {
|
|
||||||
this.md = md;
|
|
||||||
} else {
|
|
||||||
this.md = configured_markdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private render_markdown(input: MarkdownInput): MarkdownOutput {
|
|
||||||
console.log("Rendering markdown")
|
|
||||||
|
|
||||||
const content = configured_markdown().render(input.get_contents());
|
|
||||||
|
|
||||||
const result: MarkdownOutput = {
|
|
||||||
metadata: input.get_metadata(),
|
|
||||||
contents: content
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private render_html(inputs: MarkdownInput): MarkdownOutput {
|
|
||||||
const result: MarkdownOutput = {
|
|
||||||
metadata: inputs.get_metadata(),
|
|
||||||
contents: inputs.get_contents()
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(input: MarkdownInput): MarkdownOutput {
|
|
||||||
switch (input.type) {
|
|
||||||
case "markdown":
|
|
||||||
return this.render_markdown(input);
|
|
||||||
case "html":
|
|
||||||
return this.render_html(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function configured_markdown(): MarkdownIt {
|
|
||||||
var md: MarkdownIt = 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 = md
|
|
||||||
.use(tasklist)
|
|
||||||
.use(mark)
|
|
||||||
.use(footnote)
|
|
||||||
.use(alert, {
|
|
||||||
alertNames: [
|
|
||||||
"note", "info", "warning", "danger", "todo", "tip",
|
|
||||||
"important", "success", "caution", "question", "done",
|
|
||||||
"quote", "deprecated", "example"
|
|
||||||
],
|
|
||||||
}).use(tab, {
|
|
||||||
name: "tabs"
|
|
||||||
});
|
|
||||||
|
|
||||||
md.renderer.rules.text = function (tokens, idx, options, env, self) {
|
|
||||||
// headers 1-3 get an <hr> after them - With a class (md-hr-N) for styling
|
|
||||||
if (tokens[idx].type === "heading_open") {
|
|
||||||
const level = tokens[idx].tag;
|
|
||||||
return `<${level} class="md-hr-${level}">${tokens[idx + 1].content}</${level}>`;
|
|
||||||
}
|
|
||||||
return self.renderToken(tokens, idx, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>";
|
|
||||||
};
|
|
||||||
|
|
||||||
md.renderer.rules.text = function (tokens, idx, options, env, self) {
|
|
||||||
return tokens[idx].content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return md;
|
|
||||||
}
|
|
||||||
|
|
||||||
export var globalMarkdown = new MarkdownContext(undefined);
|
|
||||||
|
|
||||||
export default {
|
|
||||||
MarkdownInput,
|
|
||||||
MarkdownContext,
|
|
||||||
globalMarkdown
|
|
||||||
}
|
|
|
@ -1,117 +1,124 @@
|
||||||
{
|
{
|
||||||
"last_generated": "2025-01-03 21:53:59",
|
"last_generated": "2025-01-04 10:34:37",
|
||||||
"posts": [
|
"categories": {
|
||||||
{
|
"Blog": {
|
||||||
"metadata": {
|
"posts": [
|
||||||
"title": "3DS Programming - Using RomFS",
|
{
|
||||||
"description": "A guide to using RomFS on the 3DS. (Old)",
|
"metadata": {
|
||||||
"date": "2025-01-01 00:00:00",
|
"title": "LGBTQ+ Resources",
|
||||||
"tags": [
|
"description": "A list of resources for LGBTQ+ individuals",
|
||||||
"3ds",
|
"date": "2025-01-01 16:00:00",
|
||||||
"programming",
|
"tags": [
|
||||||
"c",
|
"lgbtq+",
|
||||||
"devkitpro",
|
"resources"
|
||||||
"old"
|
]
|
||||||
],
|
},
|
||||||
"previous": "old3ds_helloworld.md",
|
"id": "blog/lgbtq_resources.md",
|
||||||
"next": "old3ds_touchscreen.md"
|
"url": "/blog/lgbtq_resources.md",
|
||||||
},
|
"hash": "3da76064aa95cc06937bde01128ed44aafb850f35a43bd214ce0cd89a875c674"
|
||||||
"id": "/old3ds_romfs.md",
|
},
|
||||||
"url": "/blog/old3ds_romfs.md",
|
{
|
||||||
"hash": "0b28a366868e9fa564b6a33d9b1aa1d8269f7971497f25488f05f54929e88410"
|
"metadata": {
|
||||||
},
|
"title": "3DS Programming - Using RomFS",
|
||||||
{
|
"description": "A guide to using RomFS on the 3DS. (Old)",
|
||||||
"metadata": {
|
"date": "2024-12-31 16:00:00",
|
||||||
"title": "Styling Test",
|
"tags": [
|
||||||
"description": "A test post to see how the site styling looks",
|
"3ds",
|
||||||
"date": "2025-01-01 00:00:00",
|
"programming",
|
||||||
"tags": [
|
"c",
|
||||||
"meta",
|
"devkitpro",
|
||||||
"web"
|
"old"
|
||||||
]
|
],
|
||||||
},
|
"previous": "old3ds_helloworld.md",
|
||||||
"id": "/styling_test.md",
|
"next": "old3ds_touchscreen.md"
|
||||||
"url": "/blog/styling_test.md",
|
},
|
||||||
"hash": "8e6c14fdef5fd67ea17dcc8b58d59f2040d8250c15c2d18d3e1fdc1b1b60dc54"
|
"id": "blog/old3ds_romfs.md",
|
||||||
},
|
"url": "/blog/old3ds_romfs.md",
|
||||||
{
|
"hash": "0b28a366868e9fa564b6a33d9b1aa1d8269f7971497f25488f05f54929e88410"
|
||||||
"metadata": {
|
},
|
||||||
"title": "Awesome",
|
{
|
||||||
"description": "A curated list of awesome stuff I like",
|
"metadata": {
|
||||||
"date": "2024-11-26 00:00:00",
|
"title": "3DS Programming - Touchscreen Input",
|
||||||
"tags": [
|
"description": "A guide to using the touchscreen on the 3DS. (Old)",
|
||||||
"awesome",
|
"date": "2024-12-31 16:00:00",
|
||||||
"curated"
|
"tags": [
|
||||||
]
|
"3ds",
|
||||||
},
|
"programming",
|
||||||
"id": "/awesome.md",
|
"c",
|
||||||
"url": "/blog/awesome.md",
|
"devkitpro",
|
||||||
"hash": "0632400858006b93f2f36d87953538c2a400bacc75aaa29928aee226e8b343b1"
|
"old"
|
||||||
},
|
],
|
||||||
{
|
"previous": "old3ds_romfs.md"
|
||||||
"metadata": {
|
},
|
||||||
"title": "LGBTQ+ Resources",
|
"id": "blog/old3ds_touchscreen.md",
|
||||||
"description": "A list of resources for LGBTQ+ individuals",
|
"url": "/blog/old3ds_touchscreen.md",
|
||||||
"date": "2025-01-02 00:00:00",
|
"hash": "59e0b9d701646fd5f747713832c47ce451e0ebe0975d4a148a820ca795741c2b"
|
||||||
"tags": [
|
},
|
||||||
"lgbtq+",
|
{
|
||||||
"resources"
|
"metadata": {
|
||||||
]
|
"title": "Styling Test",
|
||||||
},
|
"description": "A test post to see how the site styling looks",
|
||||||
"id": "/lgbtq_resources.md",
|
"date": "2024-12-31 16:00:00",
|
||||||
"url": "/blog/lgbtq_resources.md",
|
"tags": [
|
||||||
"hash": "1dd63f74b3f74077510f1043cb471ab9c691711545bb6ef1f3d6eff4f6a23c89"
|
"meta",
|
||||||
},
|
"web"
|
||||||
{
|
]
|
||||||
"metadata": {
|
},
|
||||||
"title": "3DS Programming - Hello World",
|
"id": "blog/styling_test.md",
|
||||||
"description": "A guide to creating a simple Hello, World program for the 3DS. (Old)",
|
"url": "/blog/styling_test.md",
|
||||||
"date": "2025-01-01 00:00:00",
|
"hash": "0ff9f34321a27f462ca26656a1dc5024c0e800ea1e176ff36316b158ab4606c9"
|
||||||
"tags": [
|
},
|
||||||
"3ds",
|
{
|
||||||
"programming",
|
"metadata": {
|
||||||
"c",
|
"title": "3DS Programming - Hello World",
|
||||||
"devkitpro",
|
"description": "A guide to creating a simple Hello, World program for the 3DS. (Old)",
|
||||||
"old"
|
"date": "2024-12-31 16:00:00",
|
||||||
],
|
"tags": [
|
||||||
"next": "old3ds_romfs.md"
|
"3ds",
|
||||||
},
|
"programming",
|
||||||
"id": "/old3ds_helloworld.md",
|
"c",
|
||||||
"url": "/blog/old3ds_helloworld.md",
|
"devkitpro",
|
||||||
"hash": "86e0bd1deae0d00b17ab0960634ea7292d6387063f70600cec4001564fde9514"
|
"old"
|
||||||
},
|
],
|
||||||
{
|
"next": "old3ds_romfs.md"
|
||||||
"metadata": {
|
},
|
||||||
"title": "3DS Programming - Touchscreen Input",
|
"id": "blog/old3ds_helloworld.md",
|
||||||
"description": "A guide to using the touchscreen on the 3DS. (Old)",
|
"url": "/blog/old3ds_helloworld.md",
|
||||||
"date": "2025-01-01 00:00:00",
|
"hash": "86e0bd1deae0d00b17ab0960634ea7292d6387063f70600cec4001564fde9514"
|
||||||
"tags": [
|
},
|
||||||
"3ds",
|
{
|
||||||
"programming",
|
"metadata": {
|
||||||
"c",
|
"title": "Badges!",
|
||||||
"devkitpro",
|
"description": "A collection of 88x31 badges for various things",
|
||||||
"old"
|
"date": "2024-12-20 16:00:00",
|
||||||
],
|
"tags": [
|
||||||
"previous": "old3ds_romfs.md"
|
"badges",
|
||||||
},
|
"retro",
|
||||||
"id": "/old3ds_touchscreen.md",
|
"web"
|
||||||
"url": "/blog/old3ds_touchscreen.md",
|
]
|
||||||
"hash": "59e0b9d701646fd5f747713832c47ce451e0ebe0975d4a148a820ca795741c2b"
|
},
|
||||||
},
|
"id": "blog/badges.md",
|
||||||
{
|
"url": "/blog/badges.md",
|
||||||
"metadata": {
|
"hash": "338ccfecc6523dff93708330a8b43af715f1e80d55e1cc3bea2d1a7306fc4f00"
|
||||||
"title": "Badges!",
|
},
|
||||||
"description": "A collection of 88x31 badges for various things",
|
{
|
||||||
"date": "2024-12-21 00:00:00",
|
"metadata": {
|
||||||
"tags": [
|
"title": "Awesome",
|
||||||
"badges",
|
"description": "A curated list of awesome stuff I like",
|
||||||
"retro",
|
"date": "2024-11-25 16:00:00",
|
||||||
"web"
|
"tags": [
|
||||||
]
|
"awesome",
|
||||||
},
|
"curated"
|
||||||
"id": "/badges.md",
|
]
|
||||||
"url": "/blog/badges.md",
|
},
|
||||||
"hash": "338ccfecc6523dff93708330a8b43af715f1e80d55e1cc3bea2d1a7306fc4f00"
|
"id": "blog/awesome.md",
|
||||||
|
"url": "/blog/awesome.md",
|
||||||
|
"hash": "0632400858006b93f2f36d87953538c2a400bacc75aaa29928aee226e8b343b1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"tags": []
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
|
@ -9,9 +9,9 @@ import Card from './Card.vue';
|
||||||
<Card>
|
<Card>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<NuxtLink href="/" class="transition text-xl pl-2 pr-2 ease-in-out text-purple-100 hover:text-purple-400 duration-200">Home</NuxtLink>
|
<NuxtLink href="/" class="transition text-xl pl-2 pr-2 ease-in-out text-purple-100 hover:text-purple-400 duration-200">Home</NuxtLink>
|
||||||
<NuxtLink href="/blog/" class="transition text-xl pl-2 pr-2 ease-in-out text-purple-100 hover:text-purple-400 duration-200">Blog</NuxtLink>
|
<NuxtLink href="/article/" class="transition text-xl pl-2 pr-2 ease-in-out text-purple-100 hover:text-purple-400 duration-200">Blog</NuxtLink>
|
||||||
<NuxtLink href="/blog/?post=/blog/awesome.md" class="transition text-xl pl-2 pr-2 ease-in-out text-purple-100 hover:text-purple-400 duration-200">Awesome</NuxtLink>
|
<NuxtLink href="/article/blog/awesome.md" class="transition text-xl pl-2 pr-2 ease-in-out text-purple-100 hover:text-purple-400 duration-200">Awesome</NuxtLink>
|
||||||
<NuxtLink href="/blog/?post=/blog/badges.md" class="transition text-xl pl-2 pr-2 ease-in-out text-purple-100 hover:text-purple-400 duration-200">Badges</NuxtLink>
|
<NuxtLink href="/article/blog/badges.md" class="transition text-xl pl-2 pr-2 ease-in-out text-purple-100 hover:text-purple-400 duration-200">Badges</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center text-white">
|
<div class="flex justify-center text-white">
|
||||||
<small class="ml-3 mr-3">Hosted with <NuxtLink href="https://github.com/TheFelidae/thefelidae.github.io" class="text-blue-500">GitHub Pages</NuxtLink></small>
|
<small class="ml-3 mr-3">Hosted with <NuxtLink href="https://github.com/TheFelidae/thefelidae.github.io" class="text-blue-500">GitHub Pages</NuxtLink></small>
|
||||||
|
|
|
@ -70,7 +70,7 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink :href="'/blog?post=' +url">
|
<NuxtLink :href="'/article' + url">
|
||||||
<!-- Large -->
|
<!-- Large -->
|
||||||
<div v-if = "size === 'full'">
|
<div v-if = "size === 'full'">
|
||||||
<div class="m-4 min-h-30 min-width-90 text-white transition hover:bg-purple-600 bg-opacity-50 hover:bg-opacity-70">
|
<div class="m-4 min-h-30 min-width-90 text-white transition hover:bg-purple-600 bg-opacity-50 hover:bg-opacity-70">
|
||||||
|
|
|
@ -2,27 +2,20 @@ import * as pages from '~/utils/page_updater/update_pagelist';
|
||||||
|
|
||||||
const blog_list: pages.PageList = (await import('./assets/meta/blog_list.json')) as pages.PageList;
|
const blog_list: pages.PageList = (await import('./assets/meta/blog_list.json')) as pages.PageList;
|
||||||
|
|
||||||
const blog_routes: any = blog_list.posts.map((post) => {
|
// nitro only needs string array
|
||||||
return {
|
const blog_nitro_routes: any = [];
|
||||||
['/blog?post=' + post.id]: {
|
// key value
|
||||||
prerender: true
|
for (let [key, category] of Object.entries(blog_list.categories)) {
|
||||||
}
|
for (let post of category.posts) {
|
||||||
|
blog_nitro_routes.push('/article' + post.url);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
console.log(blog_nitro_routes);
|
||||||
blog_routes.push({
|
|
||||||
'/blog': {
|
|
||||||
prerender: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(blog_routes);
|
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: '2024-11-01',
|
compatibilityDate: '2024-11-01',
|
||||||
ssr: true,
|
ssr: true,
|
||||||
routeRules: blog_routes,
|
|
||||||
postcss: {
|
postcss: {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
|
@ -43,6 +36,11 @@ export default defineNuxtConfig({
|
||||||
// ... options
|
// ... options
|
||||||
|
|
||||||
},
|
},
|
||||||
|
nitro: {
|
||||||
|
prerender: {
|
||||||
|
routes: blog_nitro_routes
|
||||||
|
}
|
||||||
|
},
|
||||||
particles: {
|
particles: {
|
||||||
mode: 'slim',
|
mode: 'slim',
|
||||||
lazy: true
|
lazy: true
|
||||||
|
|
144
pages/article/[category]/[id].vue
Normal file
144
pages/article/[category]/[id].vue
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, watch, ref } from 'vue';
|
||||||
|
import fm from 'front-matter';
|
||||||
|
|
||||||
|
import PostCard from '~/components/PostCard.vue';
|
||||||
|
import * as pages from '~/utils/page_updater/update_pagelist';
|
||||||
|
import type { PageInfo, PageInfoMetdata } from '~/utils/page_updater/pages';
|
||||||
|
import type { ParsedContent } from '@nuxt/content';
|
||||||
|
|
||||||
|
let route = useRoute()
|
||||||
|
console.log(route)
|
||||||
|
|
||||||
|
const url: Ref<string> = ref("")
|
||||||
|
url.value = '/' + route.params.category.concat('/' + route.params.id as string) as string
|
||||||
|
|
||||||
|
console.log(url.value)
|
||||||
|
|
||||||
|
const loading: Ref<boolean> = ref(false)
|
||||||
|
|
||||||
|
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.params.category.concat('/' + route.params.id as string) as string
|
||||||
|
if (url.value) {
|
||||||
|
console.log("Fetching article")
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await fetchArticle(url.value)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
immediate: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch the article contents from the URL
|
||||||
|
async function fetchArticle(url: string) {
|
||||||
|
if (!url) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 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 article")
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log("Fetching article :3")
|
||||||
|
await fetchArticle(url.value)
|
||||||
|
})
|
||||||
|
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" />
|
||||||
|
<!-- Article Viewer -->
|
||||||
|
<div class="mt-8 flex-col text-center">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</template>
|
91
pages/article/index.vue
Normal file
91
pages/article/index.vue
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, watch, ref } from 'vue';
|
||||||
|
import fm from 'front-matter';
|
||||||
|
|
||||||
|
import PostCard from '~/components/PostCard.vue';
|
||||||
|
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 article_list: pages.PageList = await import('~/assets/meta/blog_list.json') as pages.PageList;
|
||||||
|
|
||||||
|
let route = useRoute()
|
||||||
|
console.log(route)
|
||||||
|
|
||||||
|
const loading: Ref<boolean> = ref(false)
|
||||||
|
|
||||||
|
const listCategoryKeys: Ref<string[]> = ref([])
|
||||||
|
|
||||||
|
const tagList: Ref<string[]> = ref([])
|
||||||
|
const tagFilter: Ref<string[]> = ref([])
|
||||||
|
tagFilter.value = []
|
||||||
|
|
||||||
|
function resetReadingPosition() {
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Extract the tags from each post in each category
|
||||||
|
var tags: string[] = []
|
||||||
|
for (const category in article_list.categories) {
|
||||||
|
console.log("Category: " + category)
|
||||||
|
for (const post of article_list.categories[category].posts) {
|
||||||
|
if (post.metadata.tags) {
|
||||||
|
for (const tag of post.metadata.tags) {
|
||||||
|
if (!tags.includes(tag)) {
|
||||||
|
console.log("Adding tag: " + tag)
|
||||||
|
tags.push(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tagList.value = tags
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative z-50 flex w-full justify-center text-white">
|
||||||
|
<!-- Metadata -->
|
||||||
|
<MetaSet title="Articles" description="Ramblings." background="https://avatars.githubusercontent.com/u/94077364?v=4"
|
||||||
|
tags="blog, personal, author" />
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="mt-8 flex-col text-center">
|
||||||
|
<Transition name="list">
|
||||||
|
<div>
|
||||||
|
<!-- Article List -->
|
||||||
|
<h1>Articles</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>
|
||||||
|
<div v-for="categoryKey in Object.keys(article_list.categories)" :key="categoryKey">
|
||||||
|
<div class="lg:w-[48rem] md:w-max flex flex-col bg-secondary bg-opacity-50 rounded-md shadow-md shadow-secondary p-2 m-2">
|
||||||
|
<h2>{{ categoryKey }}</h2>
|
||||||
|
<div
|
||||||
|
v-for="post in tagFilter.length == 0 ? article_list.categories[categoryKey].posts : article_list.categories[categoryKey].posts.filter((post) => post.metadata.tags ? post.metadata.tags.some((tag) => tagFilter.includes(tag)) : true)">
|
||||||
|
<PostCard class="lg:w-[48rem]" :url="post.url" :key="post.id" :tagFilter="tagFilter" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
215
pages/blog.vue
215
pages/blog.vue
|
@ -1,215 +0,0 @@
|
||||||
<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>
|
|
|
@ -13,7 +13,7 @@ const { data } = await useAsyncData('about_me', () => queryContent('/about_me').
|
||||||
<template>
|
<template>
|
||||||
<div class="relative flex w-full justify-center text-white">
|
<div class="relative flex w-full justify-center text-white">
|
||||||
<!-- Metadata -->
|
<!-- Metadata -->
|
||||||
<MetaSet title="TheFelidae" description="TheFelidae's personal site :3" background="https://avatars.githubusercontent.com/u/94077364?v=4"
|
<MetaSet title="Home" description="TheFelidae's personal site" background="https://avatars.githubusercontent.com/u/94077364?v=4"
|
||||||
tags="home, personal, author" />
|
tags="home, personal, author" />
|
||||||
<div class="mt-8 flex-col text-center">
|
<div class="mt-8 flex-col text-center">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
|
|
|
@ -3,6 +3,14 @@ import * as path from 'node:path';
|
||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
import fm, { type FrontMatterResult } from 'front-matter';
|
import fm, { type FrontMatterResult } from 'front-matter';
|
||||||
|
|
||||||
|
export interface PageLocation {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
map: string;
|
||||||
|
root: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PageInfoMetdata {
|
export interface PageInfoMetdata {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
@ -24,6 +32,13 @@ export interface PageInfo {
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PageInfoCategory {
|
||||||
|
posts: PageInfo[];
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
// Function to get metadata from a file
|
// Function to get metadata from a file
|
||||||
function getMetadata(filePath: string): PageInfoMetdata {
|
function getMetadata(filePath: string): PageInfoMetdata {
|
||||||
const fileContent = fs.readFileSync(filePath).toString();
|
const fileContent = fs.readFileSync(filePath).toString();
|
||||||
|
@ -51,17 +66,19 @@ function getWordCount(filePath: string): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get pages info
|
// Function to get pages info
|
||||||
export function getPagesInfo(searchDirectory: string, rootDirectory: string): Record<string, PageInfo> {
|
export function getPagesInfo(searchDirectory: string, pageLocation: PageLocation): Record<string, PageInfo> {
|
||||||
const pageInfo: Record<string, PageInfo> = {};
|
const pageInfo: Record<string, PageInfo> = {};
|
||||||
const currentDirectory = path.join(rootDirectory, searchDirectory);
|
const currentDirectory = path.join(pageLocation.root, searchDirectory);
|
||||||
const files = fs.readdirSync(currentDirectory);
|
const files = fs.readdirSync(currentDirectory);
|
||||||
|
console.log(files);
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
const fullPath = path.join(currentDirectory, file);
|
const fullPath = path.join(currentDirectory, file);
|
||||||
const localPath = fullPath.replace(rootDirectory, '');
|
const localPath = fullPath.replace(pageLocation.root, pageLocation.map);
|
||||||
|
console.log(fullPath);
|
||||||
|
console.log(localPath);
|
||||||
if (fs.lstatSync(fullPath).isDirectory()) {
|
if (fs.lstatSync(fullPath).isDirectory()) {
|
||||||
Object.assign(pageInfo, getPagesInfo(path.join(searchDirectory, file), rootDirectory));
|
Object.assign(pageInfo, getPagesInfo(path.join(searchDirectory, file), pageLocation));
|
||||||
} else if (file.endsWith('.md')) {
|
} else if (file.endsWith('.md')) {
|
||||||
const metadata = getMetadata(fullPath);
|
const metadata = getMetadata(fullPath);
|
||||||
const sha256Hash = getSha256Hash(fullPath);
|
const sha256Hash = getSha256Hash(fullPath);
|
||||||
|
|
|
@ -10,13 +10,19 @@ export interface Page {
|
||||||
hash: string;
|
hash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PageCategory {
|
||||||
|
posts: Page[];
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
export interface PageList {
|
export interface PageList {
|
||||||
last_generated: string;
|
last_generated: string;
|
||||||
posts: Page[];
|
categories: Record<string, PageCategory>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to generate the page list
|
// Function to generate the page list
|
||||||
function generatePageList(pagesInfo: Record<string, any>): PageList {
|
function generatePageCategory(pagesInfo: Record<string, any>): PageCategory {
|
||||||
const pageList: Page[] = [];
|
const pageList: Page[] = [];
|
||||||
|
|
||||||
for (const [path, page] of Object.entries(pagesInfo)) {
|
for (const [path, page] of Object.entries(pagesInfo)) {
|
||||||
|
@ -35,16 +41,49 @@ function generatePageList(pagesInfo: Record<string, any>): PageList {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const pageListDict: PageList = {
|
const pageListDict: PageCategory = {
|
||||||
last_generated: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
posts: pageList,
|
||||||
posts: pageList
|
title: "",
|
||||||
|
description: "",
|
||||||
|
tags: []
|
||||||
};
|
};
|
||||||
|
|
||||||
return pageListDict;
|
return pageListDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the page list and print it
|
const postDirectories: pages.PageLocation[] = [
|
||||||
const postList = generatePageList(pages.getPagesInfo("", "content/blog"));
|
{
|
||||||
|
title: "Blog",
|
||||||
|
description: "A collection of blog posts",
|
||||||
|
tags: ["blog"],
|
||||||
|
map: "blog",
|
||||||
|
root: "content/blog"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
var postList: PageList = {
|
||||||
|
last_generated: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
||||||
|
categories: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const postDirectory of postDirectories) {
|
||||||
|
const pagesInfo = pages.getPagesInfo("", postDirectory);
|
||||||
|
postList.categories[postDirectory.title] = generatePageCategory(pagesInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the posts by date
|
||||||
|
for (const category of Object.values(postList.categories)) {
|
||||||
|
category.posts.sort((a, b) => {
|
||||||
|
if (!a.metadata.date) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!b.metadata.date) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log(JSON.stringify(postList, null, 2));
|
console.log(JSON.stringify(postList, null, 2));
|
||||||
|
|
||||||
// Output to assets/blog_list.json (overwriting)
|
// Output to assets/blog_list.json (overwriting)
|
||||||
|
|
Loading…
Add table
Reference in a new issue