feat(blog): add /blog page generation, add /blog preview generation

This commit is contained in:
Monty 2021-09-15 06:50:01 +02:00
parent 8d50d0b559
commit a78baa1b61
No known key found for this signature in database
GPG Key ID: 78B405B6520E1012
10 changed files with 3476 additions and 51 deletions

20
blogposts/_example.md Normal file
View File

@ -0,0 +1,20 @@
---
title: "Title"
author: "Monty"
author_image: "https://www.github.com/montylion.png"
date: "13 September 2021"
caption: "The caption that will show up on the card for this post on the /blogs page"
cover_image: "An url to the image that will also show up on the card for this post on the /blogs page"
---
This is a series of paragraphs. According to Wikipedia, a paragraph (from the Ancient Greek παράγραφος, parágraphos, "to write beside") is a self-contained unit of discourse in writing dealing with a particular point or idea.
A paragraph consists of one or more sentences. Though not required by the syntax of any language, paragraphs are usually an expected part of formal writing, used to organize longer prose.
*This example uses material from the Wikipedia article ["Paragraph"](https://en.wikipedia.org/wiki/Paragraph), which is released under the [Creative Commons Attribution-Share-Alike License 3.0](https://creativecommons.org/licenses/by-sa/3.0/)*
![image](https://images-ext-2.discordapp.net/external/uh2DBuQss29KtEKJEmIkIUArxAhKbpmxfcTrS9dvLao/https/pbs.twimg.com/media/E_DzxqmX0AIv8Sn.jpg%3Alarge?width=526&height=702)
<iframe src="https://www.youtube.com/embed/wjWQwRlmNho" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Blogposts whose filename starts with a _ will not show up on the /blogs page, but will still be accessible from the url.

View File

@ -0,0 +1,24 @@
---
title: "Progress report for Sept. 2021"
author: "The Dev Team"
author_image: "https://www.github.com/PretendoNetwork.png"
date: "14 Sept. 2021"
caption: "Here's our progress report for September 2021 <strong>(early draft, do not leak)</strong>"
cover_image: "https://media.discordapp.net/attachments/413884110667251722/884271460724658216/image0.jpg"
---
This is just a test post.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis placerat sapien nibh, a elementum urna finibus sed. Aliquam semper venenatis tempor. Donec tincidunt suscipit dapibus. Integer id diam dictum risus hendrerit efficitur. Sed in blandit arcu, ac feugiat libero. Donec quis accumsan leo, ac scelerisque sapien.
![image](https://media.discordapp.net/attachments/413884110667251722/884271460724658216/image0.jpg)
Mauris non blandit tortor. Quisque tristique maximus lorem vel malesuada. Donec nulla eros, vehicula at odio at, fermentum tempus turpis. Duis tempor augue metus, id posuere est lobortis at. Maecenas eget accumsan justo, eget aliquam quam. Etiam gravida lacus ac nibh maximus fermentum. Etiam gravida dolor at libero lacinia sollicitudin.
<iframe src="https://www.youtube.com/embed/NW0Ir9tRSJY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Pellentesque dictum ligma arcu, eget molestie nulla porta id. Ut luctus felis turpis, a fringilla neque venenatis non. Phasellus mattis tristique dui id lobortis. Mauris facilisis risus nec pellentesque commodo.
Sed facilisis molestie eros vulputate accumsan. Phasellus feugiat auctor lacus, eu varius arcu tempor nec. Phasellus sodales aliquam mi non fringilla. Proin elementum velit in nulla iaculis malesuada.
Bottom text.

15
blogposts/todo.md Normal file
View File

@ -0,0 +1,15 @@
---
title: "Todo"
author: "Monty"
author_image: "https://www.github.com/montylion.png"
date: "19 January 2038"
caption: "A todo list"
cover_image: "https://i.imgur.com/sOWLwO5.png"
---
- <input type="checkbox" onclick="return false;">
Redesign by the one true design god jvs, then
- <input type="checkbox" onclick="return false;">
Add mobile support
- <input type="checkbox" onclick="return false;">
Add custom opengraph for each post

3222
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,9 +23,14 @@
"express-handlebars": "^4.0.4",
"express-locale": "^2.0.0",
"fs-extra": "^9.1.0",
"gray-matter": "^4.0.3",
"ioredis": "^4.26.0",
"marked": "^3.0.4",
"morgan": "^1.10.0",
"redis-json": "^5.0.0",
"trello": "^0.10.0"
},
"devDependencies": {
"eslint": "^7.32.0"
}
}

View File

@ -1,6 +1,36 @@
.all-blog-posts {
max-width: 900px;
.blog-card {
padding: 0;
margin: 0 auto;
max-width: 1100px;
margin-top: 30px;
text-decoration: none;
display: flex;
flex-flow: row nowrap;
position: relative;
}
.blog-card .left {
flex: 2 0 55%;
padding: 40px;
}
.blog-card .right {
display: flex;
border-radius: 0 10px 10px 0;
height: 260px;
width: 45%;
}
.blog-card .right img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 0 10px 10px 0;
}
.blog-card h2 {
color: white;
margin-top: 0;
}
.blog-card p, .post-info {
@ -11,22 +41,17 @@
font-weight: bold;
color: var(--text);
}
.blog-card iframe {
width: 100%;
aspect-ratio: 16/9;
}
.blog-card img {
max-width: 100%;
margin: 10px auto;
display: block;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.blog-card .post-info {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: left;
position: absolute;
bottom: 40px;
}
.blog-card .post-info > * + * {
margin-left: .5em;
@ -38,4 +63,9 @@
font-weight: bold;
color: var(--text);
align-items: center;
height: 32px; /* So the post doesn't shift when the image gets loaded */
}
.blog-card .profile img {
border: 1px solid rgba(255, 255, 255, 0.1);
max-width: 100%;
}

View File

@ -0,0 +1,58 @@
.blog-card {
padding: 60px;
margin: 0 auto;
max-width: 1100px;
margin-top: 50px;
}
.blog-card .title {
margin: 0;
}
.blog-card p, .post-info {
color: var(--text-secondary);
margin-bottom: 30px;
}
.blog a {
color: var(--text-secondary);
}
.blog-card .date {
margin-bottom: 0;
font-weight: bold;
color: var(--text);
}
.blog-card iframe {
width: 100%;
aspect-ratio: 16/9;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.blog-card img {
max-width: 100%;
margin: 10px auto;
display: block;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.blog-card .post-info {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.blog-card .post-info > * + * {
margin-left: .5em;
}
.blog-card .profile {
display: inline-grid;
grid-template-columns: 30px auto;
grid-gap: 10px;
font-weight: bold;
color: var(--text);
align-items: center;
height: 52px; /* So the post doesn't shift when the image gets loaded */
}
footer {
margin-top: 80px;
}

View File

@ -2,6 +2,10 @@ const { Router } = require('express');
const util = require('../util');
const router = new Router();
const fs = require('fs');
const path = require('path');
const marked = require('marked');
const matter = require('gray-matter');
router.get('/', async (request, response) => {
@ -10,10 +14,54 @@ router.get('/', async (request, response) => {
const localeString = reqLocale.toString();
const fileList = fs.readdirSync('blogposts');
// We get the info for each blogpost, ignoring the ones starting with _
const postList = fileList
.filter(filename => !filename.startsWith('_'))
.map((filename) => {
const slug = filename.replace('.md', '');
const rawPost = fs.readFileSync(path.join('blogposts', `${filename}`), 'utf-8');
const { data: postInfo } = matter(rawPost);
return {
slug, postInfo
};
});
postList.sort((a, b) => {
return new Date(b.postInfo.date) - new Date(a.postInfo.date);
});
response.render('blog', {
layout: 'main',
locale,
localeString,
postList
});
});
router.get('/:slug', async (request, response) => {
const reqLocale = request.locale;
const locale = util.getLocale(reqLocale.region, reqLocale.language);
const localeString = reqLocale.toString();
// Get the name of the post from the URL
const postName = request.params.slug;
// Get the markdown file corresponding to the post
const rawPost = fs.readFileSync(path.join('blogposts', `${postName}.md`), 'utf-8');
// Convert the post info into JSON and separate it and the content
const { data: postInfo, content } = matter(rawPost);
// Convert the content into HTML
const htmlPost = marked(content);
response.render('blogpost', {
layout: 'main',
locale,
localeString,
postInfo,
htmlPost
});
});

View File

@ -12,47 +12,26 @@
</div>
</div>
<div class="all-blog-posts">
<div class="purple-card blog-card">
<div class="post-info">
<span>Published by</span>
<div class="profile">
<img class="profile-picture" src="https://jipfr.nl/icon.png" alt>
<span>Jip Frijlink</span>
{{#each postList }}
<a href="/blog/{{this.slug}}" class="purple-card blog-card">
<div class="left">
<h2>{{{ this.postInfo.title }}}</h2>
<p>{{{ this.postInfo.caption }}}</p>
<div class="post-info">
<span>Published by</span>
<div class="profile">
<img class="profile-picture" src="{{{ this.postInfo.author_image }}}" alt>
<span>{{{ this.postInfo.author }}}</span>
</div>
<span>on</span>
<span class="date">{{{ this.postInfo.date }}}</span>
</div>
<span>on</span>
<span class="date">1 Sept. 2021</span>
</div>
<h2>Progress report for Sept. 2021</h2>
<p>It is, but that's the point of the Patreon. Hopefully the Patreon will take off and I can make Pretendo development a full time job</p>
<p>The way game servers work on the 3DS and WiiU is through a service called NEX, which Nintendo licensed from a different company</p>
<p>NEX acts as a middleware basically. It provides a set of protocols (auth, secure connection, matchmaking, etc) and each protocol has a set of methods associated with it and each method tends to have its own dedicated custom types</p>
<img src="https://images-ext-2.discordapp.net/external/uh2DBuQss29KtEKJEmIkIUArxAhKbpmxfcTrS9dvLao/https/pbs.twimg.com/media/E_DzxqmX0AIv8Sn.jpg%3Alarge?width=526&height=702">
<p>So like every game implements AuthenticationProtocol::Login and AuthenticationProtocol::LoginEx to login with, both of which provide a AuthenticationInfo data structure</p>
<p>Every game server is made up of a mix of implemented protocols and supported methods. The trouble is that Nintendo basically hacked NEX support into their games so a lot of stuff isn't clear because NEX is a set of generic protocols and methods, and Nintendo tends to abuse them and even fork them into Nintendo-specific protocols and methods</p>
<p>For example NEX provides a protocol called DataStore, which is basically a file database with some metadata associated with the files. SMM forks DataStore into it's own DataStore(SMM) protocol, which several new (sometimes replaced) methods</p>
<p>SMM is basically 90% vanilla DataStore and DataStore(SMM), but even though Nintendo forked DataStore into their own protocol they still reuse a lot of the same generic DataStore types and data structures which leads to confusing cases of unused and weirdly used fields</p>
<iframe src="https://www.youtube.com/embed/wjWQwRlmNho" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>Like for example in DataStore(SMM) there's a method called DataStore(SMM)::RecommendedCourseSearchObject which is called to get the Course World list, and it returns a list of DataStoreCustomRankingResult data structures. Inside this data structure is 2 notable fields; MetaBinary and Ratings. MetaBinary is a NEX::Buffer type which has data like the course type, some crc32's, etc. Ratings though is the weird one. It actually doesn't store any ratings for the course, that's what the Score field does. Ratings is used to track the number of times people have attempted the course</p>
<p>But because these protocols and methods are very generic and shared between multiple games, it means knowledge of one game can't really be reused for another. Like Splatoon also implements DataStore, and also forks it into DataStore(Splatoon), and things like MetaBinary in that game are completely different</p>
<p>And then to top it all off, there are MANY versions of NEX being used by Nintendo so several games are also slightly different at the transport level (like does the server support acknowledging multiple packets at once, do the data structures have a header or not, etc). And Nintendo would update the NEX library per-game at times without documenting any version changes internally, so it's all guess work at the start as to what each game supports</p>
</div>
</div>
<div class="right">
<img src="{{this.postInfo.cover_image}}" class="cover" />
</div>
</a>
{{/each}}
{{> footer }}

26
views/blogpost.handlebars Normal file
View File

@ -0,0 +1,26 @@
<link rel="stylesheet" href="/assets/css/blogpost.css" />
<div class="wrapper blog">
{{> header}}
<div class="purple-card blog-card">
<h1 class="title">{{{ postInfo.title }}}</h1>
<div class="post-info">
<span>Published by</span>
<div class="profile">
<img class="profile-picture" src="{{ postInfo.author_image }}" alt="{{ postInfo.author }}">
<span>{{{ postInfo.author }}}</span>
</div>
<span>on</span>
<span class="date">{{{ postInfo.date }}}</span>
</div>
{{{ htmlPost }}}
</div>
{{> footer }}
</div>