add css, next post feature
change favicon, add more readme, add extra example post, dark/light mode toggle
This commit is contained in:
40
README.md
40
README.md
@@ -1,26 +1,56 @@
|
||||
# Hedgeblog
|
||||
My [personal blog](https://www.prussiafan.club), because what the world needs is yet another semi-abandoned blog.
|
||||
My [personal blog](https://www.prussiafan.club), because what the world needs is yet another semi-abandoned blog. All the code in this repo is licensed under the AGPL license, with the exception of Makoto, which is licensed under the MIT license.
|
||||
|
||||
## Technical Goals
|
||||
- Completely rewrite the blog, and get it working
|
||||
- Be able to be served statically (so it can be deployed on Github Pages or Cloudflare Pages for no dinero)
|
||||
- No dependencies - or basically, I want to write every line of code
|
||||
- No dependencies - or basically, I want to write every line of code (builtin modules like `path`, `fs` are ok of course)
|
||||
- No Javascript served to client - the web pages should be pure HTML and CSS
|
||||
|
||||
These goals are accomplished!
|
||||
|
||||
## Non-Technical Goals
|
||||
- Make two things I can call "Ryuji" and "Saki" to go along with "Makoto" (those are the three main characters of one of the best manga series ever)
|
||||
- Move over some of the old blog posts (only the stuff I like), after rewriting them
|
||||
- Start writing stuff on the blog again, at least semi-regularly
|
||||
|
||||
The third goal may never be accomplished.
|
||||
|
||||
## Makoto
|
||||
Makoto is the markdown-to-html parser, made with no dependencies. It was made around two months before Ryuji and Saki, and is meant to be more of a standalone thing. This is the sole npm dependency of the project (because I published makoto to npm and wanted to make sure it worked).
|
||||
Makoto is the markdown-to-html parser, made with no dependencies. It was made around two months before Ryuji and Saki, and is meant to be more of a standalone thing. This is the sole npm dependency of the project. I `npm install`ed it instead of just copying the file over mostly because I published Makoto to npm and wanted to make sure it worked. Also it has different license, documentation and stuff.
|
||||
|
||||
It also has a very cool warnings feature, that isn't used in this project, but can be seen in action if you use the [Makoto Web Editor](https://makoto.prussia.dev).
|
||||
|
||||
## Ryuji
|
||||
Ryuji is a simple templating system. It's Jinja/Nunjucks inspired but has less features. On the upside, Ryuji is around 200 lines of code and supports if statements, for loops, components and inserting variables.
|
||||
Ryuji is a simple, Jinja/Nunjucks inspired templating system that supports `if` statements, `for` loops, components, and inserting variables. It isn't quite as fully featured as Jinja/Nunjucks, but on the upside, Ryuji is around just 200 lines of code, and worked very well for my usecase. I think it's pretty cool.
|
||||
|
||||
I didn't write any docs for it, but you can see the syntax if you look in the `templates` directory or look in `tests.ts`.
|
||||
I didn't write any docs for it (yet), but you can see the syntax if you look in the `templates` directory or look in `tests.ts`.
|
||||
|
||||
## Saki
|
||||
Saki is the build system that puts it all together and outputs the blog's static html. Even more simple than Ryuji, it is just around 70 lines of code.
|
||||
|
||||
## Running
|
||||
First, install the dependencies (well, dependency, since Makoto is the only one).
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Building
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Previewing
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
This builds the project and then serves the `build` folder at [http://localhost:8042](http://localhost:8042). As you can see in `preview.ts`, this part also relies on no dependencies - only builtin module `http` is used.
|
||||
|
||||
## Tests for Ryuji (templating)
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
Uses Endosulfan, my very basic <40 LOC test assertion thingy.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export let total_tests: number = 0;
|
||||
export let failed_tests: number = 0;
|
||||
export let passed_tests: number = 0;
|
||||
|
||||
4
index.ts
4
index.ts
@@ -50,7 +50,7 @@ let tags: string[] = []; //also get all the tags since we are iterating through
|
||||
for (let i=0; i < posts_metadata.length; i++) {
|
||||
let post_metadata: PostMetadata = posts_metadata[i];
|
||||
posts_serve_paths.push(`/posts/${post_metadata.slug}`);
|
||||
let post_md_path: string = path.join(__dirname, `/posts/${post_metadata.slug}.md`);
|
||||
let post_md_path: string = path.join(__dirname, `/posts/${post_metadata.filename}.md`);
|
||||
let md: string = readFileSync(post_md_path, "utf-8").replaceAll("\r", "");
|
||||
let html: string = parse_md_to_html(md);
|
||||
for (let j=0; j < post_metadata.tags.length; j++) {
|
||||
@@ -65,9 +65,11 @@ for (let i=0; i < posts_metadata.length; i++) {
|
||||
html,
|
||||
tags_exist: post_metadata.tags.length !== 0,
|
||||
}
|
||||
let next_post: PostMetadata = posts_metadata[i+1] ? posts_metadata[i+1] : posts_metadata[0];
|
||||
posts_vars.push(
|
||||
{
|
||||
post,
|
||||
next_post,
|
||||
author_expected: post.author.toLowerCase().startsWith("jetstream0") || post.author.toLowerCase().startsWith("prussia"),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -6,5 +6,13 @@
|
||||
"date": "30/12/1999",
|
||||
"author": "Prussia",
|
||||
"tags": ["example", "fake", "please remember to remove"]
|
||||
},
|
||||
"another_example": {
|
||||
"title": "Another Example!",
|
||||
"slug": "another-example",
|
||||
"filename": "another_example",
|
||||
"date": "31/07/2023",
|
||||
"author": "John Dough",
|
||||
"tags": ["example", "jia", "please remember to remove"]
|
||||
}
|
||||
}
|
||||
14
posts/another_example.md
Normal file
14
posts/another_example.md
Normal file
@@ -0,0 +1,14 @@
|
||||
Have some lorem ipsum. On the house!
|
||||
...
|
||||
No, I **insist**. Don't be shy.
|
||||
|
||||
1. lorem
|
||||
2. ipsum
|
||||
|
||||
Repellat et vel consequuntur et. Suscipit animi ipsam tempora consequatur hic ea tenetur. Culpa rerum quos eum vero ea.
|
||||
Aperiam quia facilis doloremque ducimus. Amet quas similique officia quas et enim aut. Non ut vel sint distinctio consectetur ipsa.
|
||||
Illum id sit laboriosam corrupti veritatis et quam. Ut ea quaerat omnis doloribus enim. Sed atque ad est doloribus esse.
|
||||
|
||||
Molestias omnis ut voluptatem. Eius vel deleniti quia quam odio. Adipisci voluptas dolor nulla voluptatem. Molestiae sunt veritatis qui ex atque molestiae nobis. Expedita repellat dolores adipisci.
|
||||
|
||||
Deleniti totam molestiae necessitatibus rem dolores. Natus ut quos beatae aut. Corrupti eum provident perferendis eum dolores maiores eos.
|
||||
@@ -2,7 +2,7 @@ Hello! This is an **example** post *not meant* to be actually served on the blog
|
||||
|
||||
[look its a link](https://example.com)
|
||||
|
||||

|
||||

|
||||
|
||||
## Wow a header
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.9 KiB |
84
static/styles/global.css
Normal file
84
static/styles/global.css
Normal file
@@ -0,0 +1,84 @@
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#main {
|
||||
box-sizing: border-box;
|
||||
min-height: 100vh;
|
||||
padding: 35px 10vw;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
}
|
||||
|
||||
*:not(h1, h2, h3, h4, h5, h6) {
|
||||
font-size: 1.02rem;
|
||||
}
|
||||
|
||||
#return {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: forestgreen;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: orchid;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 7px 0px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: Verdana, sans-serif;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#dark-mode {
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#dark-mode:checked ~ #main {
|
||||
background-color: #222;
|
||||
color: white;
|
||||
}
|
||||
|
||||
label[for="dark-mode"] {
|
||||
position: fixed;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
#dark-mode + label[for="dark-mode"]::after {
|
||||
content: "🌙";
|
||||
}
|
||||
|
||||
#dark-mode:checked + label[for="dark-mode"]::after {
|
||||
content: "☀️";
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
h2 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#fancy-title:checked ~ h2 {
|
||||
color: white;
|
||||
padding-right: 0px 4px;
|
||||
background-color: dodgerblue;
|
||||
text-shadow: 1px 1px 1px gray, 4px 4px 1px blue;
|
||||
}
|
||||
|
||||
#checkboxes {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#post-md {
|
||||
margin-top: 7px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#show-md:checked ~ #post-md {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#show-md:checked ~ #post-html {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#next-post-container {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
1
templates/components/dark-mode-checkbox.html
Normal file
1
templates/components/dark-mode-checkbox.html
Normal file
@@ -0,0 +1 @@
|
||||
<input id="dark-mode" type="checkbox"/><label for="dark-mode"></label>
|
||||
35
templates/components/makoto-styling.html
Normal file
35
templates/components/makoto-styling.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<style>
|
||||
blockquote {
|
||||
margin: 0;
|
||||
margin: 8px 0px;
|
||||
padding: 4px 8px 4px 0px;
|
||||
border-left: 8px solid #727272;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.code-block, code {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
color: #222;
|
||||
background-color: #d8d8d8;
|
||||
padding: 2px 4px;
|
||||
word-break: break-all;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 2px 5px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
@@ -1 +1 @@
|
||||
<a href="/" style="position: absolute; left: 10px; top: 10px;"><- Get me outta here!</a>
|
||||
<a id="return" href="/"><= Get me outta here!</a>
|
||||
@@ -5,8 +5,19 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>prussia fan club</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/styles/global.css">
|
||||
<link rel="stylesheet" href="/styles/index.css">
|
||||
</head>
|
||||
<body>
|
||||
[[ component:dark-mode-checkbox ]]
|
||||
<div id="main">
|
||||
<div>
|
||||
<input id="fancy-title" type="checkbox"/><label for="fancy-title">Fancy Title</label>
|
||||
<br>
|
||||
<h2>prussiafan.club</h2>
|
||||
<br>
|
||||
<p>This is my blog. I also have a <a href="https://prussia.dev">portfolio</a>, <a href="https://prussia.dev/retro">retro style personal website</a>, and a place where you can <a href="https://prussia.dev/sample">hire me</a>.</p>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
[[ for:posts:post ]]
|
||||
@@ -14,8 +25,6 @@
|
||||
[[ endfor ]]
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p>This is my blog. I also have a <a href="https://prussia.dev">portfolio</a>, <a href="https://prussia.dev/retro">retro style personal website</a>, and a place where you can <a href="https://prussia.dev/sample">hire me</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,17 +5,20 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>[[ post.title ]]</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/styles/global.css">
|
||||
<link rel="stylesheet" href="/styles/post.css">
|
||||
</head>
|
||||
<body>
|
||||
[[ component:return ]]
|
||||
<div>
|
||||
[[ component:dark-mode-checkbox ]]
|
||||
<div id="main">
|
||||
<div id="post-info">
|
||||
<h1>[[ post.title ]]</h1>
|
||||
<div>
|
||||
<span><span [[ if:author_expected ]]title="(obviously)"[[ endif ]]>By [[ post.author ]]</span> | <span>[[ post.date ]]</span> [[ if:post.tags_exist ]]| <span>[[ for:post.tags:tag:index:max ]]<a href="/tags/[[ tag ]]">[[ tag ]]</a>[[ if:index:!max ]], [[ endif ]][[ endfor ]]</span>[[ endif ]]</span>
|
||||
</div>
|
||||
</div>
|
||||
<input id="show-md" type="checkbox"/><label for="html">Show MD</label>
|
||||
<input id="show-md" type="checkbox"/><label for="show-md">Show MD</label>
|
||||
<div id="post-html">
|
||||
[[ html:post.html ]]
|
||||
</div>
|
||||
@@ -27,6 +30,12 @@
|
||||
[[ endif ]]
|
||||
[[ endfor ]]
|
||||
</div>
|
||||
[[ if:post.slug:!next_post.slug ]]
|
||||
<div id="next-post-container">
|
||||
<a href="/posts/[[ next_post.slug ]]">Next Post: [[ next_post.title ]]</a>
|
||||
</div>
|
||||
[[ endif ]]
|
||||
[[ component:makoto-styling ]]
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,8 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>"[[ tag ]]" posts</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/styles/global.css">
|
||||
</head>
|
||||
<body>
|
||||
[[ component:return ]]
|
||||
[[ component:dark-mode-checkbox ]]
|
||||
<div id="main">
|
||||
<div>
|
||||
<h2>Search for posts with tag "[[ tag ]]"</h2>
|
||||
</div>
|
||||
@@ -17,5 +21,6 @@
|
||||
[[ endfor ]]
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user