Introduce new markdown parsing method in projects and proj slugs

This commit is contained in:
Matthew Barnes 2023-06-24 00:58:09 +01:00
parent 0ef064455b
commit ddd9254c58
Signed by: mb2g17
GPG Key ID: 2D2CFBEE0B64E30B
12 changed files with 263 additions and 36 deletions

113
package-lock.json generated
View File

@ -17,6 +17,7 @@
"daisyui": "^2.52.0",
"dayjs": "^1.11.8",
"marked": "^5.1.0",
"mdsvex": "^0.10.6",
"postcss": "^8.4.24",
"svelte": "^3.54.0",
"svelte-check": "^3.0.1",
@ -788,6 +789,12 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
"node_modules/@types/unist": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
"dev": true
},
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@ -1606,6 +1613,21 @@
"node": ">= 18"
}
},
"node_modules/mdsvex": {
"version": "0.10.6",
"resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.10.6.tgz",
"integrity": "sha512-aGRDY0r5jx9+OOgFdyB9Xm3EBr9OUmcrTDPWLB7a7g8VPRxzPy4MOBmcVYgz7ErhAJ7bZ/coUoj6aHio3x/2mA==",
"dev": true,
"dependencies": {
"@types/unist": "^2.0.3",
"prism-svelte": "^0.4.7",
"prismjs": "^1.17.1",
"vfile-message": "^2.0.4"
},
"peerDependencies": {
"svelte": "3.x"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -1980,6 +2002,21 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"node_modules/prism-svelte": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/prism-svelte/-/prism-svelte-0.4.7.tgz",
"integrity": "sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ==",
"dev": true
},
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -2522,6 +2559,19 @@
"node": ">=14.0"
}
},
"node_modules/unist-util-stringify-position": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
"integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
"dev": true,
"dependencies": {
"@types/unist": "^2.0.2"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/update-browserslist-db": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
@ -2564,6 +2614,20 @@
"integrity": "sha512-fvieEbHy1ZS23zrcX+topzqAgA4Uct1enngOEWLFBgs9TtOf6RDFOYatH7KSVdrABzQDMCQ5myQy+nTSZZwLzg==",
"dev": true
},
"node_modules/vfile-message": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
"integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
"dev": true,
"dependencies": {
"@types/unist": "^2.0.0",
"unist-util-stringify-position": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/vite": {
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
@ -3092,6 +3156,12 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
"@types/unist": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
"dev": true
},
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@ -3688,6 +3758,18 @@
"integrity": "sha512-z3/nBe7aTI8JDszlYLk7dDVNpngjw0o1ZJtrA9kIfkkHcIF+xH7mO23aISl4WxP83elU+MFROgahqdpd05lMEQ==",
"dev": true
},
"mdsvex": {
"version": "0.10.6",
"resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.10.6.tgz",
"integrity": "sha512-aGRDY0r5jx9+OOgFdyB9Xm3EBr9OUmcrTDPWLB7a7g8VPRxzPy4MOBmcVYgz7ErhAJ7bZ/coUoj6aHio3x/2mA==",
"dev": true,
"requires": {
"@types/unist": "^2.0.3",
"prism-svelte": "^0.4.7",
"prismjs": "^1.17.1",
"vfile-message": "^2.0.4"
}
},
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -3925,6 +4007,18 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"prism-svelte": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/prism-svelte/-/prism-svelte-0.4.7.tgz",
"integrity": "sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ==",
"dev": true
},
"prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"dev": true
},
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -4287,6 +4381,15 @@
"busboy": "^1.6.0"
}
},
"unist-util-stringify-position": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
"integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
"dev": true,
"requires": {
"@types/unist": "^2.0.2"
}
},
"update-browserslist-db": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
@ -4309,6 +4412,16 @@
"integrity": "sha512-fvieEbHy1ZS23zrcX+topzqAgA4Uct1enngOEWLFBgs9TtOf6RDFOYatH7KSVdrABzQDMCQ5myQy+nTSZZwLzg==",
"dev": true
},
"vfile-message": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
"integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
"dev": true,
"requires": {
"@types/unist": "^2.0.0",
"unist-util-stringify-position": "^2.0.0"
}
},
"vite": {
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",

View File

@ -19,6 +19,7 @@
"daisyui": "^2.52.0",
"dayjs": "^1.11.8",
"marked": "^5.1.0",
"mdsvex": "^0.10.6",
"postcss": "^8.4.24",
"svelte": "^3.54.0",
"svelte-check": "^3.0.1",

View File

@ -1,7 +1,6 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { fade, scale } from 'svelte/transition';
import { marked } from 'marked';
import Badge from '$lib/components/Badge.svelte';
import Carousel from '$lib/components/Carousel.svelte';
import type { Project } from '$lib/types';
@ -47,11 +46,6 @@
function onOutsideClick() {
dispatch('click-outside');
}
$: descriptionHtml = marked.parse(project.description, {
mangle: false,
headerIds: false,
});
</script>
<input type="checkbox" id="my-modal-4" class="modal-toggle" checked />
@ -118,7 +112,7 @@
</div>
<article class="prose text-sm mt-4 pr-3 max-w-full max-h-description md:overflow-y-auto text-justify text-base-content">
{@html descriptionHtml}
<svelte:component this={project.content} />
</article>
<div class="modal-action">

View File

@ -34,7 +34,8 @@ export interface MinimalProject {
export type Project = MinimalProject & {
dateOfCompletion: Date;
description: string;
/** Component to render description */
content: any;
screenshots: number;

View File

@ -34,20 +34,14 @@ export const markdownToEmployment = (markdown: string, slug: string): Employment
};
}
export const markdownToProject = (markdown: string, slug: string): Project => {
// Get front matter and content
const sliced = markdown.split("---");
const frontMatter = parse(sliced[1]);
const content = sliced[2].trim();
// Populate project object
export const markdownToProject = (frontMatter: any, content: any, slug: string): Project => {
return {
name: frontMatter.name,
slug,
dateOfCompletion: new Date(frontMatter.dateOfCompletion),
summary: frontMatter.summary,
description: content,
content,
type: (!frontMatter.genres) || (frontMatter.genres?.length === 0) ? ProjectType.APPLICATION : ProjectType.GAME,
languages: frontMatter.languages ?? [],
@ -57,9 +51,9 @@ export const markdownToProject = (markdown: string, slug: string): Project => {
screenshots: frontMatter.screenshots,
demoUrl: frontMatter["demo-url"] ?? "",
releaseUrl: frontMatter["release-url"] ?? "",
sourceUrl: frontMatter["source-url"] ?? "",
demoUrl: frontMatter.demoUrl ?? "",
releaseUrl: frontMatter.releaseUrl ?? "",
sourceUrl: frontMatter.sourceUrl ?? "",
starred: frontMatter.starred ?? false,
};

View File

@ -1,15 +1,42 @@
import type { Project } from '$lib/types';
import { markdownToProject } from '$lib/utils';
export const prerender = false;
export const load = (async ({ url, fetch }) => {
const response = await fetch("/_api/projects.json");
const json = await response.json();
export const load = (async ({ url }) => {
// Get all projects
const mdFiles = import.meta.glob("./*.md");
let projects: Project[] = [];
for (const mdPath in mdFiles) {
const rawProj: any = await mdFiles[mdPath]();
const slug = mdPath.slice(2, -3);
const proj = markdownToProject(rawProj.metadata, rawProj.default, slug);
projects = [...projects, proj];
}
// Sort projects by date
projects.sort((p1, p2) => p2.dateOfCompletion.getTime() - p1.dateOfCompletion.getTime());
// Get languages, frameworks, etc.
const languages: Set<string> = new Set();
const frameworks: Set<string> = new Set();
const technologies: Set<string> = new Set();
const genres: Set<string> = new Set();
for (const proj of projects) {
proj.languages.forEach(l => languages.add(l));
proj.frameworks.forEach(f => frameworks.add(f));
proj.technologies.forEach(t => technologies.add(t));
proj.genres.forEach(g => genres.add(g));
}
return {
projects: json,
projects: projects,
queryParams: url.searchParams.toString(),
languages: (await (await fetch("/_api/languages.json")).json()),
frameworks: (await (await fetch("/_api/frameworks.json")).json()),
technologies: (await (await fetch("/_api/technologies.json")).json()),
genres: (await (await fetch("/_api/genres.json")).json()),
languages: [...languages],
frameworks: [...frameworks],
technologies: [...technologies],
genres: [...genres],
};
});

View File

@ -1,11 +1,9 @@
import type { PageLoad } from './$types';
import type { Project } from '$lib/types';
import { markdownToProject } from '$lib/utils';
export const load = (async ({ params, fetch }) => {
const response = await fetch(`/_api/projects/${params.slug}.json`);
const project = await response.json() as Project;
return {
project
};
export const load = (async ({ params }) => {
// Get current project based on slug
const rawProj = await import(`../${params.slug}.md`);
const project = markdownToProject(rawProj.metadata, rawProj.default, params.slug);
return { project };
}) satisfies PageLoad;

View File

@ -0,0 +1,22 @@
---
name: "Flappy Knuckles"
dateOfCompletion: "March 11, 2017"
summary: "A fan-game for Android made in Java and LibGDX that went viral on the Google Play Store."
languages: ["Java"]
frameworks: ["LibGDX"]
genres: ["Arcade"]
screenshots: 2
releaseUrl: "https://mb2g17.github.io/FlappyKnuckles/"
sourceUrl: "https://git.matt-barnes.co.uk/mb2g17/FlappyKnucklesSrc"
starred: true
---
I made this game in Java. It was the weekend, and I was working hard on my A Level Computer Science coursework write-up. Getting a little bit bored, I took a break. Then, I remembered an idea I had earlier that day: what if there was a Flappy Bird clone featuring Knuckles the Echidna with all of his classic quotes? I then got to work and I completed the game that day, late at night.
After releasing it on the Play Store, it started gaining downloads. People eventually started making YouTube videos about it, [such as this one](https://www.youtube.com/watch?v=Gw1VMdTwzPE). At one point, [Neb Comics](https://www.nebcomics.com/) played my game and got in touch with me about a game idea they had. [Someone even drew fan-art of the game](https://www.deviantart.com/x-silverlining-x/art/Energy-708003627).
If you've played Flappy Bird, you'll know what to expect. To play, you tap the screen to make Knuckles fly upwards. You have to time your taps to guide Knuckles through the pipes. Every time you pass through a pair of pipes, you get a point. When you hit a pipe or land on the ground, you lose. There is a high-score system which is stored locally. I know that Knuckles doesn't canonically fly like this, but I don't really care.

View File

@ -0,0 +1,20 @@
---
name: "FossPop"
dateOfCompletion: "July 08, 2021"
summary: "A free and open-source clone of the puzzle game HuniePop using WebAssembly."
languages: ["C++"]
technologies: ["WebAssembly", "Unit Testing", "CI / CD"]
genres: ["Arcade"]
screenshots: 2
demoUrl: "https://demos.matt-barnes.co.uk/fosspop/"
sourceUrl: "https://git.matt-barnes.co.uk/mb2g17/fosspop"
---
A while ago, I found out about WebAssembly, and was fascinated by the performance of web applications using it, as well as the use of practically any programming language to build them. Following from that, I wanted to create a web application that utilises WebAssembly. I saw AssemblyScript, which was nice, but it didn't have any third-party libraries for making anything particularly interesting. Then, I came across Emscripten, which is a compiler that compiles C / C++ code to WebAssembly. After creating a quick test project with SDL2, I thought I'd make a simple game to test this new technology out!
FossPop is a free and open-source clone of the puzzle dating sim HuniePop. You have a grid of coloured tiles, and you earn points by matching up adjacent tiles of the same colour. You can move a tile anywhere on the same row or column, but you can't move them diagonally. Match up bells to get extra moves, and match up hearts to increase your score multiplier. Keep clear of broken hearts, as they will decrease your score!
In addition to my first WebAssembly project, this is also my first project to use a self-hosted CI solution: Jenkins. Not only that, but the sprites designed for this were created in Krita: a free and open-source graphical editor.

View File

@ -0,0 +1,20 @@
---
name: "Gnummikub"
dateOfCompletion: "January 07, 2022"
summary: "A free-and-open-source implementation of Rummikub as an online multiplayer game."
languages: ["Go", "TypeScript"]
frameworks: ["Svelte"]
technologies: ["Unit Testing", "CI / CD"]
genres: ["Multiplayer"]
screenshots: 4
sourceUrl: "https://git.matt-barnes.co.uk/mb2g17/gnummikub"
---
[Rummikub](https://en.wikipedia.org/wiki/Rummikub) is a board game that I had been playing since I was little. Me and my friends would often play the online version of Rummikub, which is proprietary. After a while, I had a thought: is there an open-source Rummikub implementation we could host ourselves and play online? After searching online, I found no such thing.
This project is that idea brought to life: users can host a Gnummikub server, and connect to it through the client. In-game, players can play Rummikub, chat, send messages, and send emotes. At the time of writing this, it is the only free-and-open-source Rummikub online game implementation. To learn how to set up your own Gnummikub game, [check out the wiki page](https://git.matt-barnes.co.uk/mb2g17/gnummikub/wiki/Setting-up-Gnummikub).
This project also served as a learning process for me, as this is the first project of mine that makes use of the Go language and Svelte front-end framework.

View File

@ -0,0 +1,29 @@
---
name: "MyAnythingList"
dateOfCompletion: "May 20, 2023"
summary: "A Hugo theme designed similarly to MyAnimeList, but for any general ranked personal list."
languages: ["HTML", "JavaScript"]
frameworks: ["Hugo"]
screenshots: 4
demoUrl: "https://lists.matt-barnes.co.uk/"
sourceUrl: "https://git.matt-barnes.co.uk/mb2g17/MyAnythingList"
---
MyAnythingList is a Hugo theme that was designed with a similar purpose to the "Anime List" view in MyAnimeList, but designed for any kind of item, such as games, books, TV shows and more. Each entry is represented as a Markdown document, each state such as 'watching' or 'complete' is represented as sub-lists, and each entry type such as 'games' or 'films' are represented as lists. This is a reimplementation of the project [Personal Lists](personal-lists).
For a long time now, I've had many iterations of a general-purpose reimplementation of MyAnimeList that I would personally use to catalogue the games I've played, games I'm playing, and games I've beaten. The initial implementations were very rudimentary: the first being little more than an PHP server and a front-end for a text file that you could manually update with the web interface (it didn't even use a markup language like JSON or XML). Since then, I had upgraded it multiple times, until eventually I reached what you see in my Personal Lists project.
There were a number of things I didn't like about Personal Lists that I wished I could've done differently, such as:
- The awful PHP backend I whipped up because I got lazy
- The awkward UI across the top of the screen, which was useless unless you were the admin
- No built-in dark theme
Therefore, I always had the project idea of revamping Personal Lists in the back of my mind. When I got around to learning Hugo and static site generators, I thought it would be a great idea to turn Personal Lists into a Hugo theme and structure everything with Markdown documents. This would be good because:
- No more awful PHP backend; a dead simple HTTP web server would suffice
- No awkward UI; if you want to add / move / delete entries, you'd do it with the Markdown files, and commit + push to the repository
- It's super easy to integrate themes with a Hugo configuration file and DaisyUI + TailwindCSS
Thus, MyAnythingList was born!

View File

@ -1,11 +1,19 @@
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/kit/vite';
import { mdsvex } from 'mdsvex';
/** @type {import('@sveltejs/kit').Config} */
const config = {
extensions: ['.svelte', '.md'],
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
preprocess: [
vitePreprocess(),
mdsvex({
extensions: ['.md'],
}),
],
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.