Article feature initial

This commit is contained in:
Kalle 2022-08-20 16:15:03 +03:00
parent a6f4f11abf
commit 009c2a3e24
9 changed files with 284 additions and 10 deletions

61
app/routes/a.$slug.tsx Normal file
View File

@ -0,0 +1,61 @@
import Markdown from "markdown-to-jsx";
import { Main } from "~/components/Main";
import { json, type LoaderArgs, type MetaFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import * as React from "react";
import { articleBySlug } from "~/utils/markdown.server";
import invariant from "tiny-invariant";
import type { UseDataFunctionReturn } from "@remix-run/react/dist/components";
import { makeTitle } from "~/utils/strings";
import { articlePreviewUrl } from "~/utils/urls";
import { notFoundIfFalsy } from "~/utils/remix";
export const meta: MetaFunction = (args) => {
invariant(args.params["slug"]);
const data = args.data as Nullable<UseDataFunctionReturn<typeof loader>>;
if (!data) return {};
const description = data.content.trim().split("\n")[0];
return {
title: makeTitle(data.title),
description,
"og:description": description,
"twitter:card": "summary_large_image",
"og:image": articlePreviewUrl(args.params["slug"]),
};
};
export const loader = ({ params }: LoaderArgs) => {
invariant(params["slug"]);
return json(notFoundIfFalsy(articleBySlug(params["slug"])));
};
export default function ArticlePage() {
const data = useLoaderData<typeof loader>();
return (
<Main>
<article className="article">
<h1>{data.title}</h1>
<div className="text-sm text-lighter">
by <Author /> <time>{data.dateString}</time>
</div>
<Markdown options={{ wrapper: React.Fragment }}>
{data.content}
</Markdown>
</article>
</Main>
);
}
function Author() {
const data = useLoaderData<typeof loader>();
if (data.authorLink) {
return <a href={data.authorLink}>{data.author}</a>;
}
return <>{data.author}</>;
}

View File

@ -482,6 +482,10 @@ dialog::backdrop {
background-color: var(--theme-transparent);
}
.article > p {
padding-block: var(--s-2-5);
}
.alert {
display: flex;
flex-wrap: wrap;

View File

@ -0,0 +1,43 @@
import matter from "gray-matter";
import fs from "node:fs";
import { z, ZodError } from "zod";
const articleDataSchema = z.object({
title: z.string().min(1),
author: z.string().min(1),
date: z.date(),
});
export function articleBySlug(slug: string) {
try {
const rawMarkdown = fs.readFileSync(`content/articles/${slug}.md`, "utf8");
const { content, data } = matter(rawMarkdown);
const { date, ...restParsed } = articleDataSchema.parse(data);
return {
content,
dateString: date.toLocaleDateString("en-US", {
day: "2-digit",
month: "long",
year: "numeric",
}),
authorLink: authorToLink(restParsed.author),
...restParsed,
};
} catch (e) {
if (!(e instanceof Error)) throw e;
if (e.message.includes("ENOENT") || e instanceof ZodError) {
return null;
}
throw e;
}
}
function authorToLink(author: string) {
if (author === "Riczi") return "https://twitter.com/Riczi2k";
return;
}

View File

@ -39,6 +39,8 @@ export const badgeUrl = ({
code: Badge["code"];
extension?: "gif";
}) => `/badges/${code}${extension ? `.${extension}` : ""}`;
export const articlePreviewUrl = (slug: string) =>
`/img/article-previews/${slug}.png`;
export function resolveBaseUrl(url: string) {
return new URL(url).host;

View File

@ -0,0 +1,19 @@
---
title: Splatoon 3 Splatfest World Premiere
date: 2022-08-20
author: Riczi
---
The Splatoon franchise is holding a global testfire for the newest entry to their series; Splatoon 3. This will give new players a chance to try out the game for the first time before purchasing. Long-time fans of the game will also get a chance to try out new weapons and maps in anticipation for the titles release on September 9th, 2022.
Players will gain access to the Splatlands and training grounds starting on August 25th, 2022. Here, you will be able to customize your characters appearance, complete a tutorial, and test out the large arsenal weapons in the game. You can get a feel for the games unique shooting mechanics, which differ greatly from other popular shooting games.
After initial access, you can participate in the first Splatfest of Splatoon 3. A Splatfest pits teams against each other using different themes. Players commit to a team, then compete in Splatoons trademark game mode Turf War. In this mode, teams of four battle against each other to ink as much ground area as possible within a three-minute window. The team with the most ink at the end of the game wins. Simple as that!
The [Splatfest themes](https://splatoonwiki.org/wiki/Splatfest#Splatfest_team_names) always cover fun topics. In the past, teams have been divided into Cats vs. Dogs, Pirates vs. Ninjas, and Heroes vs. Villains. Turf War battles come with fun debates about the variety of topics selected as Splatfest themes.
In Splatoon 3, Splatfests will take on a new form. There will now be three teams to choose from rather than the traditional duo. The Splatfest World Premier, which will be held on August 27th, 2022, will be a classic game of Rock, Paper, Scissors. The Turf War games also come with a new twist, featuring [Tricolor Turf Wars](https://splatoonwiki.org/wiki/Tricolor_Turf_War) for the first time. In the second half of the Splatfest, the leading team will have to stave off a comeback from the other two teams in 4v2v2 matches!
You can also participate in Splatfest by checking out the Salmon Run. In this game mode, youre part of a four person squad clearing waves of Salmonids to collect their eggs. This is a fun way to try out new weapons and practice your skills while still supporting your Splatfest team.
To access the global testfire, download the free software located here. The Splatoon 3 icon will be downloaded to your Switch home screen and will be unlocked on the appropriate date. After downloading, you will also receive a weeks worth of Nintendo Online Services for free so you can participate in the festivities.

160
package-lock.json generated
View File

@ -17,12 +17,14 @@
"countries-list": "^2.6.1",
"date-fns": "^2.29.1",
"fuse.js": "^6.6.2",
"gray-matter": "^4.0.3",
"i18next": "^21.9.0",
"i18next-browser-languagedetector": "^6.1.5",
"i18next-fs-backend": "^1.1.5",
"i18next-http-backend": "^1.4.1",
"just-capitalize": "^3.1.1",
"just-shuffle": "^4.1.1",
"markdown-to-jsx": "^7.1.7",
"node-cron": "3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@ -7035,7 +7037,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
@ -8241,6 +8242,40 @@
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
"dev": true
},
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/gray-matter/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/gray-matter/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/gunzip-maybe": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz",
@ -8980,7 +9015,6 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -9593,7 +9627,6 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -9968,6 +10001,17 @@
"node": ">=0.10.0"
}
},
"node_modules/markdown-to-jsx": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz",
"integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==",
"engines": {
"node": ">= 10"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/mathml-tag-names": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@ -13068,6 +13112,29 @@
"loose-envify": "^1.1.0"
}
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/section-matter/node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -13606,6 +13673,11 @@
"node": ">=0.10.0"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
},
"node_modules/sshpk": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
@ -13783,6 +13855,14 @@
"node": ">=8"
}
},
"node_modules/strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@ -20638,8 +20718,7 @@
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.4.0",
@ -21572,6 +21651,36 @@
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
"dev": true
},
"gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"requires": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"dependencies": {
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
}
}
},
"gunzip-maybe": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz",
@ -22119,8 +22228,7 @@
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
},
"is-extglob": {
"version": "2.1.1",
@ -22581,8 +22689,7 @@
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
},
"kleur": {
"version": "4.1.4",
@ -22865,6 +22972,12 @@
"integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==",
"dev": true
},
"markdown-to-jsx": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz",
"integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==",
"requires": {}
},
"mathml-tag-names": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@ -25068,6 +25181,25 @@
"loose-envify": "^1.1.0"
}
},
"section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"requires": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -25489,6 +25621,11 @@
"extend-shallow": "^3.0.0"
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
},
"sshpk": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
@ -25631,6 +25768,11 @@
"ansi-regex": "^5.0.1"
}
},
"strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="
},
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",

View File

@ -35,12 +35,14 @@
"countries-list": "^2.6.1",
"date-fns": "^2.29.1",
"fuse.js": "^6.6.2",
"gray-matter": "^4.0.3",
"i18next": "^21.9.0",
"i18next-browser-languagedetector": "^6.1.5",
"i18next-fs-backend": "^1.1.5",
"i18next-http-backend": "^1.4.1",
"just-capitalize": "^3.1.1",
"just-shuffle": "^4.1.1",
"markdown-to-jsx": "^7.1.7",
"node-cron": "3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -24,7 +24,8 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true,
"useUnknownInCatchVariables": true
"useUnknownInCatchVariables": true,
"skipLibCheck": true
},
"ts-node": {
"transpileOnly": true