working + many improvements
ryuji new features, saki changes, write build code, bump makoto version, add preview
This commit is contained in:
@@ -18,9 +18,9 @@ Makoto is the markdown-to-html parser, made with no dependencies. It was made ar
|
|||||||
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).
|
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
|
||||||
Ryuji is a simple templating system. It's Jinja/Nunjucks inspired but has less features. On the upside, Ryuji is less than 200 lines of code and supports if statements, for loops, components and inserting variables.
|
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.
|
||||||
|
|
||||||
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, but you can see the syntax if you look in the `templates` directory or look in `tests.ts`.
|
||||||
|
|
||||||
## Saki
|
## Saki
|
||||||
Saki is the build system that puts it all together and outputs the blog's static html.
|
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.
|
||||||
|
|||||||
82
index.ts
82
index.ts
@@ -1,18 +1,92 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { parse_md_to_html } from 'makoto';
|
||||||
import { Renderer } from './ryuji.js';
|
import { Renderer } from './ryuji.js';
|
||||||
import { BlogBuilder, PostMetadata } from './saki.js';
|
import { Builder } from './saki.js';
|
||||||
import _posts_metadata from './posts/_metadata.json';
|
import _posts_metadata from './posts/_metadata.json';
|
||||||
|
|
||||||
|
export interface PostMetadata {
|
||||||
|
title: string,
|
||||||
|
slug: string,
|
||||||
|
filename: string,
|
||||||
|
date: string,
|
||||||
|
author: string,
|
||||||
|
tags: string[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Post extends PostMetadata {
|
||||||
|
md_lines: string[],
|
||||||
|
html: string,
|
||||||
|
tags_exist: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
let renderer: Renderer = new Renderer("templates", "components");
|
let renderer: Renderer = new Renderer("templates", "components");
|
||||||
let builder: BlogBuilder = new BlogBuilder(renderer);
|
let builder: Builder = new Builder();
|
||||||
|
|
||||||
let posts_metadata: PostMetadata[] = Object.values(_posts_metadata);
|
let posts_metadata: PostMetadata[] = Object.values(_posts_metadata);
|
||||||
|
|
||||||
builder.serve_static_folder("static");
|
builder.serve_static_folder("static");
|
||||||
|
|
||||||
//home page
|
//home page
|
||||||
builder.serve_template("/", "index.html", {
|
builder.serve_template(renderer, "/", "index.html", {
|
||||||
posts: posts_metadata,
|
posts: posts_metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
//blog posts
|
//blog posts
|
||||||
builder.serve_markdowns("/posts", "/posts", "post.html", posts_metadata, true);
|
|
||||||
|
//if two tags reduce down to the same slug, oh well, not my problem
|
||||||
|
function slugify(tag: string) {
|
||||||
|
let allowed_chars: string[] = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "_"];
|
||||||
|
return tag.replaceAll(" ", "_").split("").filter((char) => allowed_chars.includes(char.toLowerCase())).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
//slugify all the tags
|
||||||
|
posts_metadata.forEach((post_metadata) => post_metadata.tags = post_metadata.tags.map((tag) => slugify(tag)));
|
||||||
|
|
||||||
|
let posts_serve_paths: string[] = [];
|
||||||
|
let posts_vars: any[] = [];
|
||||||
|
let tags: string[] = []; //also get all the tags since we are iterating through all the posts
|
||||||
|
|
||||||
|
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 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++) {
|
||||||
|
let tag: string = post_metadata.tags[j];
|
||||||
|
if (!tags.includes(tag)) {
|
||||||
|
tags.push(post_metadata.tags[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let post: Post = {
|
||||||
|
...post_metadata,
|
||||||
|
md_lines: md.split("\n"),
|
||||||
|
html,
|
||||||
|
tags_exist: post_metadata.tags.length !== 0,
|
||||||
|
}
|
||||||
|
posts_vars.push(
|
||||||
|
{
|
||||||
|
post,
|
||||||
|
author_expected: post.author.toLowerCase().startsWith("jetstream0") || post.author.toLowerCase().startsWith("prussia"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.serve_templates(renderer, posts_serve_paths, "post.html", posts_vars);
|
||||||
|
|
||||||
|
//tags
|
||||||
|
|
||||||
|
let tags_serve_paths: string[] = [];
|
||||||
|
let tags_vars: any[] = [];
|
||||||
|
|
||||||
|
for (let i=0; i < tags.length; i++) {
|
||||||
|
let tag: string = tags[i];
|
||||||
|
tags_serve_paths.push(`/tags/${slugify(tag)}`);
|
||||||
|
tags_vars.push({
|
||||||
|
tag,
|
||||||
|
posts: posts_metadata.filter((post) => post.tags.includes(tag)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.serve_templates(renderer, tags_serve_paths, "tags.html", tags_vars);
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"makoto": "^1.0.0"
|
"makoto": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.4.5"
|
"@types/node": "^20.4.5"
|
||||||
@@ -22,9 +22,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/makoto": {
|
"node_modules/makoto": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/makoto/-/makoto-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/makoto/-/makoto-1.0.1.tgz",
|
||||||
"integrity": "sha512-Zl2GWYNrPqpSXZajf2rBcgOD6rtwi3gT/sc0pbjGJSPvad9uoJ0z6iMTZowfohbYHhTLeR2x3E20CQ//myyrqg=="
|
"integrity": "sha512-SV/HW0lAy/D0tuYlX00Hg1g+5uo4ak95tcObw3RcY5U/fWMlc3aJuOPzbvqaWNKtDgPafCclU1eTDuZ6ZARSvA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p . && node index.js",
|
"build": "tsc -p . && node index.js",
|
||||||
"test": "tsc -p . && node tests.js"
|
"test": "tsc -p . && node tests.js",
|
||||||
|
"preview": "npm run build && node preview.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/jetstream0/hedgeblog#readme",
|
"homepage": "https://github.com/jetstream0/hedgeblog#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"makoto": "^1.0.0"
|
"makoto": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.4.5"
|
"@types/node": "^20.4.5"
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
"filename": "example",
|
"filename": "example",
|
||||||
"date": "30/12/1999",
|
"date": "30/12/1999",
|
||||||
"author": "Prussia",
|
"author": "Prussia",
|
||||||
"tags": []
|
"tags": ["example", "fake", "please remember to remove"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,8 @@ console.log("code block!!!");
|
|||||||
```
|
```
|
||||||
|
|
||||||
> ## woah a blockquote
|
> ## woah a blockquote
|
||||||
> Yeehaw ^[1]^
|
> Yeehaw ^\[1]^
|
||||||
|
|
||||||
===
|
----
|
||||||
|
|
||||||
- [1]: Source? Me.
|
- \[1]: Source? Me.
|
||||||
|
|||||||
56
preview.ts
Normal file
56
preview.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { createServer } from 'http';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { existsSync, readFileSync } from 'fs';
|
||||||
|
|
||||||
|
const port: number = 8042;
|
||||||
|
|
||||||
|
createServer((req, res) => {
|
||||||
|
let req_path: string;
|
||||||
|
if (!req.url.includes(".")) {
|
||||||
|
req_path = path.join(__dirname, "build", req.url, "index.html");
|
||||||
|
} else {
|
||||||
|
req_path = path.join(__dirname, "build", req.url);
|
||||||
|
}
|
||||||
|
if (!existsSync(req_path)) {
|
||||||
|
res.writeHead(404);
|
||||||
|
//write file
|
||||||
|
res.write("404");
|
||||||
|
return res.end();
|
||||||
|
}
|
||||||
|
//set content type
|
||||||
|
let non_utf8_content_types: string[] = ["image/png", "image/gif"];
|
||||||
|
let content_type: string;
|
||||||
|
switch (req_path.split(".")[1]) {
|
||||||
|
case "html":
|
||||||
|
content_type = "text/html; charset=utf-8";
|
||||||
|
break;
|
||||||
|
case "css":
|
||||||
|
content_type = "text/css; charset=utf-8";
|
||||||
|
break;
|
||||||
|
case "js":
|
||||||
|
content_type = "text/javascript";
|
||||||
|
break;
|
||||||
|
case "png":
|
||||||
|
case "ico":
|
||||||
|
content_type = "image/png";
|
||||||
|
break;
|
||||||
|
case "gif":
|
||||||
|
content_type = "image/gif";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
content_type = "text/plain";
|
||||||
|
}
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': content_type,
|
||||||
|
});
|
||||||
|
//write file
|
||||||
|
if (non_utf8_content_types.includes(content_type)) {
|
||||||
|
res.write(readFileSync(req_path));
|
||||||
|
} else {
|
||||||
|
res.write(readFileSync(req_path, "utf-8"));
|
||||||
|
}
|
||||||
|
//end response
|
||||||
|
res.end();
|
||||||
|
}).listen(port);
|
||||||
|
|
||||||
|
console.log(`Preview on port ${port}`);
|
||||||
75
ryuji.ts
75
ryuji.ts
@@ -1,6 +1,6 @@
|
|||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
export const SYNTAX_REGEX = /\[\[ [a-zA-Z0-9.:\-_]+ \]\]/g;
|
export const SYNTAX_REGEX = /\[\[ [a-zA-Z0-9.:\-_!]+ \]\]/g;
|
||||||
|
|
||||||
export type file_extension = `.${string}`;
|
export type file_extension = `.${string}`;
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ export interface ForLoopInfo {
|
|||||||
current: number,
|
current: number,
|
||||||
var_value: any, //value we are looping over
|
var_value: any, //value we are looping over
|
||||||
iter_var_name?: string,
|
iter_var_name?: string,
|
||||||
|
index_var_name?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
@@ -104,6 +105,18 @@ export class Renderer {
|
|||||||
Renderer.check_var_name_legality(iter_var_name, false);
|
Renderer.check_var_name_legality(iter_var_name, false);
|
||||||
vars[iter_var_name] = var_value[0];
|
vars[iter_var_name] = var_value[0];
|
||||||
}
|
}
|
||||||
|
if (typeof exp_parts[3] === "string") {
|
||||||
|
//set index count
|
||||||
|
let index_var_name: string = exp_parts[3];
|
||||||
|
Renderer.check_var_name_legality(index_var_name, false);
|
||||||
|
vars[index_var_name] = 0;
|
||||||
|
}
|
||||||
|
if (typeof exp_parts[4] === "string") {
|
||||||
|
//set max count
|
||||||
|
let max_var_name: string = exp_parts[4];
|
||||||
|
Renderer.check_var_name_legality(max_var_name, false);
|
||||||
|
vars[max_var_name] = var_value.length-1;
|
||||||
|
}
|
||||||
//add to for loops
|
//add to for loops
|
||||||
for_loops.push({
|
for_loops.push({
|
||||||
index,
|
index,
|
||||||
@@ -111,6 +124,7 @@ export class Renderer {
|
|||||||
current: 0,
|
current: 0,
|
||||||
var_value,
|
var_value,
|
||||||
iter_var_name: exp_parts[2],
|
iter_var_name: exp_parts[2],
|
||||||
|
index_var_name: exp_parts[3],
|
||||||
});
|
});
|
||||||
//make sure thing we are iterating over isn't empty
|
//make sure thing we are iterating over isn't empty
|
||||||
if (var_value.length === 0) {
|
if (var_value.length === 0) {
|
||||||
@@ -122,16 +136,16 @@ export class Renderer {
|
|||||||
continue;*/
|
continue;*/
|
||||||
let sliced = matches.slice(index+1, matches.length);
|
let sliced = matches.slice(index+1, matches.length);
|
||||||
let new_index: number;
|
let new_index: number;
|
||||||
let extra_forss: number = 0;
|
let extra_fors: number = 0;
|
||||||
for (let i=0; i < sliced.length; i++) {
|
for (let i=0; i < sliced.length; i++) {
|
||||||
if (sliced[i][0].startsWith("[[ for:")) {
|
if (sliced[i][0].startsWith("[[ for:")) {
|
||||||
extra_forss++;
|
extra_fors++;
|
||||||
} else if (sliced[i][0] === "[[ endfor ]]") {
|
} else if (sliced[i][0] === "[[ endfor ]]") {
|
||||||
if (extra_forss === 0) {
|
if (extra_fors === 0) {
|
||||||
new_index = i;
|
new_index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
extra_forss--;
|
extra_fors--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof new_index === "undefined") throw Error("if statement missing an `[[ endif ]]`");
|
if (typeof new_index === "undefined") throw Error("if statement missing an `[[ endif ]]`");
|
||||||
@@ -151,6 +165,9 @@ export class Renderer {
|
|||||||
if (current_loop.iter_var_name) {
|
if (current_loop.iter_var_name) {
|
||||||
vars[current_loop.iter_var_name] = current_loop.var_value[current_loop.current];
|
vars[current_loop.iter_var_name] = current_loop.var_value[current_loop.current];
|
||||||
}
|
}
|
||||||
|
if (current_loop.index_var_name) {
|
||||||
|
vars[current_loop.index_var_name] = current_loop.current;
|
||||||
|
}
|
||||||
//go back to start of for loop index
|
//go back to start of for loop index
|
||||||
index = current_loop.index;
|
index = current_loop.index;
|
||||||
continue;
|
continue;
|
||||||
@@ -159,9 +176,40 @@ export class Renderer {
|
|||||||
if (typeof exp_parts[1] !== "string") throw Error("`if:` statement missing variable name afterwards");
|
if (typeof exp_parts[1] !== "string") throw Error("`if:` statement missing variable name afterwards");
|
||||||
let var_name: string = exp_parts[1];
|
let var_name: string = exp_parts[1];
|
||||||
let var_value = Renderer.get_var(var_name, vars);
|
let var_value = Renderer.get_var(var_name, vars);
|
||||||
|
let condition_pass: boolean;
|
||||||
|
if (typeof exp_parts[2] !== "string") {
|
||||||
|
//make sure var is truthy
|
||||||
if (var_value) {
|
if (var_value) {
|
||||||
//yup, nothing here
|
condition_pass = true;
|
||||||
} else {
|
} else {
|
||||||
|
condition_pass = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//compare with second var
|
||||||
|
let var_name2: string = exp_parts[2];
|
||||||
|
let if_not: boolean = false;
|
||||||
|
if (var_name2.startsWith("!")) {
|
||||||
|
var_name2 = var_name2.slice(1, var_name2.length);
|
||||||
|
if_not = true;
|
||||||
|
}
|
||||||
|
let var_value2 = Renderer.get_var(var_name2, vars);
|
||||||
|
if (if_not) {
|
||||||
|
//make sure the two compared variables are NOT equal
|
||||||
|
if (var_value !== var_value2) {
|
||||||
|
condition_pass = true;
|
||||||
|
} else {
|
||||||
|
condition_pass = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//regular comparison statement
|
||||||
|
if (var_value === var_value2) {
|
||||||
|
condition_pass = true;
|
||||||
|
} else {
|
||||||
|
condition_pass = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!condition_pass) { //failed condition
|
||||||
//skip to the endif
|
//skip to the endif
|
||||||
let sliced = matches.slice(index+1, matches.length);
|
let sliced = matches.slice(index+1, matches.length);
|
||||||
let new_index: number;
|
let new_index: number;
|
||||||
@@ -192,7 +240,20 @@ export class Renderer {
|
|||||||
} else {
|
} else {
|
||||||
var_name = exp_parts[0];
|
var_name = exp_parts[0];
|
||||||
}
|
}
|
||||||
let var_value = Renderer.get_var(var_name, vars);
|
//convert to string
|
||||||
|
let var_value: string = String(Renderer.get_var(var_name, vars));
|
||||||
|
//add indentation
|
||||||
|
let current_lines: string[] = rendered.split("\n")
|
||||||
|
let current_last: string = current_lines[current_lines.length-1];
|
||||||
|
let indentation: number = 0;
|
||||||
|
for (let i=0; i < current_last.length; i++) {
|
||||||
|
if (current_last[i] !== " ") break;
|
||||||
|
indentation++;
|
||||||
|
}
|
||||||
|
let var_lines: string[] = var_value.split("\n");
|
||||||
|
let var_first: string = var_lines.shift();
|
||||||
|
//append spaces
|
||||||
|
var_value = var_lines.length === 0 ? var_first : var_first+"\n"+var_lines.map((var_line) => " ".repeat(indentation)+var_line).join("\n");
|
||||||
if (exp_parts[0] === "html") {
|
if (exp_parts[0] === "html") {
|
||||||
//variable but not sanitized
|
//variable but not sanitized
|
||||||
rendered += var_value;
|
rendered += var_value;
|
||||||
|
|||||||
69
saki.ts
69
saki.ts
@@ -1,7 +1,6 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { copyFileSync, existsSync, readdirSync, rmSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
import { copyFileSync, existsSync, readdirSync, rmSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
||||||
import type { Renderer } from './ryuji.js';
|
import type { Renderer } from './ryuji.js';
|
||||||
import { parse_md_to_html } from 'makoto';
|
|
||||||
|
|
||||||
export class Builder {
|
export class Builder {
|
||||||
build_dir: string;
|
build_dir: string;
|
||||||
@@ -44,9 +43,11 @@ export class Builder {
|
|||||||
let dest_path: string = path.join(this.build_dir, serve_path);
|
let dest_path: string = path.join(this.build_dir, serve_path);
|
||||||
if (!serve_path.includes(".")) {
|
if (!serve_path.includes(".")) {
|
||||||
//serve as index.html in serve_path directory
|
//serve as index.html in serve_path directory
|
||||||
if (dest_path !== this.build_dir && dest_path !== path.join(this.build_dir, "/")) {
|
|
||||||
//will not make a new directory if `serve_path` is "/", since the build directory already exists
|
//will not make a new directory if `serve_path` is "/", since the build directory already exists
|
||||||
mkdirSync(dest_path);
|
if (dest_path !== this.build_dir && dest_path !== path.join(this.build_dir, "/")) {
|
||||||
|
mkdirSync(dest_path, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
writeFileSync(path.join(dest_path, "index.html"), content);
|
writeFileSync(path.join(dest_path, "index.html"), content);
|
||||||
} else {
|
} else {
|
||||||
@@ -60,68 +61,14 @@ export class Builder {
|
|||||||
this.serve_content(file_content, serve_path);
|
this.serve_content(file_content, serve_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
_serve_template(renderer: Renderer, serve_path: string, template_name: string, vars: any) {
|
serve_template(renderer: Renderer, serve_path: string, template_name: string, vars: any) {
|
||||||
let content: string = renderer.render_template(template_name, vars);
|
let content: string = renderer.render_template(template_name, vars);
|
||||||
this.serve_content(content, serve_path);
|
this.serve_content(content, serve_path);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//this code is more or less specific to my blog
|
serve_templates(renderer: Renderer, serve_paths: string[], template_name: string, vars_array: any[]) {
|
||||||
|
for (let i=0; i < serve_paths.length; i++) {
|
||||||
export interface PostMetadata {
|
this.serve_template(renderer, serve_paths[i], template_name, vars_array[i]);
|
||||||
title: string,
|
|
||||||
slug: string,
|
|
||||||
filename: string,
|
|
||||||
date: string,
|
|
||||||
author: string,
|
|
||||||
tags: string[],
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Post extends PostMetadata {
|
|
||||||
md: string,
|
|
||||||
html: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BlogBuilder extends Builder {
|
|
||||||
renderer: Renderer;
|
|
||||||
|
|
||||||
constructor(renderer: Renderer, build_dir: string="/build") {
|
|
||||||
super(build_dir);
|
|
||||||
this.renderer = renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
serve_template(serve_path: string, template_name: string, vars: any) {
|
|
||||||
super._serve_template(this.renderer, serve_path, template_name, vars);
|
|
||||||
}
|
|
||||||
|
|
||||||
serve_markdown(serve_path: string, template_name: string, markdown_post: Post, additional_vars: any={}) {
|
|
||||||
additional_vars.post = markdown_post;
|
|
||||||
this.serve_template(serve_path, template_name, {
|
|
||||||
post: markdown_post,
|
|
||||||
author_expected: markdown_post.author.toLowerCase().startsWith("jetstream0") || markdown_post.author.toLowerCase().startsWith("prussia"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
serve_markdowns(serve_path: string, posts_path: string, template_name: string, posts_metadata: PostMetadata[], own_dir: boolean=true) {
|
|
||||||
let posts_dir_path: string = path.join(this.build_dir, posts_path);
|
|
||||||
if (!existsSync(posts_dir_path)) {
|
|
||||||
mkdirSync(posts_dir_path);
|
|
||||||
}
|
|
||||||
for (let i=0; i < posts_metadata.length; i++) {
|
|
||||||
let post_metadata: PostMetadata = posts_metadata[i];
|
|
||||||
let post_md_path: string = path.join(__dirname, posts_path, `${post_metadata.slug}.md`);
|
|
||||||
let post_md: string = readFileSync(post_md_path, "utf-8");
|
|
||||||
let post_html: string = parse_md_to_html(post_md);
|
|
||||||
let post: Post = {
|
|
||||||
...post_metadata,
|
|
||||||
md: post_md,
|
|
||||||
html: post_html,
|
|
||||||
};
|
|
||||||
if (own_dir) {
|
|
||||||
this.serve_markdown(path.join(serve_path, post.slug), template_name, post);
|
|
||||||
} else {
|
|
||||||
this.serve_markdown(path.join(serve_path, `${post.slug}.html`), template_name, post);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<a style="position: absolute; left: 10px; top: 10px;"><- Get me outta here!</a>
|
<a href="/" style="position: absolute; left: 10px; top: 10px;"><- Get me outta here!</a>
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>prussia fan club</title>
|
<title>prussia fan club</title>
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>[[ post.title ]]</title>
|
<title>[[ post.title ]]</title>
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
[[ component:return ]]
|
[[ component:return ]]
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
<div id="post-info">
|
<div id="post-info">
|
||||||
<h1>[[ post.title ]]</h1>
|
<h1>[[ post.title ]]</h1>
|
||||||
<div>
|
<div>
|
||||||
<span><span [[ if:author_expected ]]title="(obviously)"[[ endif ]]>By [[ post.author ]]</span> | <span>[[ post.date ]]</span></span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<input id="show-md" type="checkbox"/><label for="html">Show MD</label>
|
<input id="show-md" type="checkbox"/><label for="html">Show MD</label>
|
||||||
@@ -19,7 +20,12 @@
|
|||||||
[[ html:post.html ]]
|
[[ html:post.html ]]
|
||||||
</div>
|
</div>
|
||||||
<div id="post-md">
|
<div id="post-md">
|
||||||
[[ post.md ]]
|
[[ for:post.md_lines:line ]]
|
||||||
|
[[ line ]]
|
||||||
|
[[ if:line ]]
|
||||||
|
<br>
|
||||||
|
[[ endif ]]
|
||||||
|
[[ endfor ]]
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
21
templates/tags.html
Normal file
21
templates/tags.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>"[[ tag ]]" posts</title>
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h2>Search for posts with tag "[[ tag ]]"</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
[[ for:posts:post ]]
|
||||||
|
[[ component:post-listing ]]
|
||||||
|
[[ endfor ]]
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
57
tests.ts
57
tests.ts
@@ -116,4 +116,61 @@ test_assert_equal(
|
|||||||
"for loop with template test"
|
"for loop with template test"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//[[ if index_var ]] will be false when index_var is 0 btw
|
||||||
|
test_assert_equal(
|
||||||
|
renderer.render(
|
||||||
|
"[[ for:trees:_tree:index_var ]][[ if:index_var ]]<b>[[ index_var ]]</b>[[ endif ]][[ endfor ]]",
|
||||||
|
{
|
||||||
|
trees: [
|
||||||
|
"mango",
|
||||||
|
"oak",
|
||||||
|
"redwood",
|
||||||
|
"palm",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"<b>1</b><b>2</b><b>3</b>",
|
||||||
|
"for loop with index test",
|
||||||
|
);
|
||||||
|
|
||||||
|
test_assert_equal(
|
||||||
|
renderer.render(
|
||||||
|
"[[ for:trees:_tree:index_var:max_var ]][[ index_var ]]/[[ max_var ]][[ if:index_var:!max_var ]] [[ endif ]][[ endfor ]]",
|
||||||
|
{
|
||||||
|
trees: [
|
||||||
|
"mango",
|
||||||
|
"oak",
|
||||||
|
"redwood",
|
||||||
|
"palm",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"0/3 1/3 2/3 3/3",
|
||||||
|
"for loop with index, max and if statement test",
|
||||||
|
);
|
||||||
|
|
||||||
|
test_assert_equal(
|
||||||
|
renderer.render(
|
||||||
|
"[[ if:nutritious:delicious ]]meets both[[ endif ]]",
|
||||||
|
{
|
||||||
|
nutritious: "yes",
|
||||||
|
delicious: "yes",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"meets both",
|
||||||
|
"if statement with comparisons test",
|
||||||
|
);
|
||||||
|
|
||||||
|
test_assert_equal(
|
||||||
|
renderer.render(
|
||||||
|
"[[ if:nutritious:!delicious ]]fails both[[ endif ]][[ if:nutritious:!yes ]]this never displays[[ endif ]]",
|
||||||
|
{
|
||||||
|
nutritious: "yes",
|
||||||
|
delicious: "no",
|
||||||
|
yes: "yes",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"fails both",
|
||||||
|
"if not statement with comparisons test",
|
||||||
|
);
|
||||||
log_test_results();
|
log_test_results();
|
||||||
|
|||||||
Reference in New Issue
Block a user