31 Commits
Author SHA1 Message Date
Raymonzut 8d5e9c02ad Merge branch 'beta' into master 2021-01-19 17:48:53 +01:00
Raymonzut a0eb56e115 Remove dynamic age feature 2021-01-19 17:40:48 +01:00
Raymonzut 6c31c8752d Join date sort order function to one line
- Fixes not using year in order calculation
2021-01-08 21:33:28 +01:00
Raymonzut 8e15354c2a Add ExamplePost 2020-11-26 23:08:08 +01:00
Raymonzut 3f888cbeb8 Merge branch 'beta' into master 2020-11-13 19:53:01 +01:00
Raymonzut f82b6ba8f6 Add docker-compose.yaml for client 2020-11-12 18:14:56 +01:00
Raymonzut bf0b5448b6 Fix #13 2020-11-12 17:36:40 +01:00
Raymonzut 6271fbb3b0 Rename gen.ex to gen.exs, as it is a script 2020-11-07 11:45:20 +01:00
Raymonzut eff86a1f3e Fix #12
- move pre-gen files out of public/
2020-11-07 11:43:14 +01:00
Raymonzut 403fc9d5cb Account for multiple years for sorting posts
- Prepare for 2021
2020-11-06 22:38:10 +01:00
Raymonzut 67b8a4f581 Update index gen for new date format 2020-11-06 22:18:27 +01:00
Raymonzut e9d50f1392 Extract template filling: pair usage consistant 2020-10-09 23:31:54 +02:00
Raymonzut 5d2fd6674f Merge branch 'beta' into master 2020-10-09 17:35:18 +02:00
Raymonzut 2a128f49ab Add extra experiment to README 2020-10-09 00:43:25 +02:00
Raymonzut b5403fbc0c Add README.md 2020-10-09 00:41:43 +02:00
Raymonzut 39aff9d684 Remove node ignorefile remnants 2020-10-02 16:39:42 +02:00
Raymonzut 90eff2d619 Merge branch 'beta' into master 2020-10-02 16:02:59 +02:00
Raymonzut 1f7c52a37b Remove docker-compose.yml 2020-10-02 16:02:38 +02:00
Raymonzut 277ec48ee6 Merge branch 'replacejs/server' into beta 2020-10-02 14:38:11 +02:00
Raymonzut cf4c84df5f Rewrite RSS post feed to be generated 2020-10-02 14:37:45 +02:00
Raymonzut a358896c03 Rename gen related files to their purpose 2020-10-02 14:35:56 +02:00
Raymonzut 3e0462b74b Nuke entire node fastify server 2020-10-02 13:43:50 +02:00
Raymonzut 1fc48b52e3 Link href css from root 2020-10-02 00:41:05 +02:00
Raymonzut 0850426683 Merge branch 'replacejs/posts' into beta 2020-10-01 20:08:52 +02:00
Raymonzut 6c67113227 Rewrite post related pages to be generated 2020-10-01 20:07:31 +02:00
Raymonzut b8f41fe691 Remove js for posts page 2020-09-26 19:10:58 +02:00
raymon 1ba2a430fe Remove extra chromosome broken JS date constructor 2020-09-26 18:37:56 +02:00
Raymonzut 9d44aa5d4e Convert raw post content to HTML
- Contains \n shorthand for a paragraph
2020-09-26 18:28:06 +02:00
Raymonzut 4b0f683901 Remove unneeded curly braces 2020-09-26 18:25:41 +02:00
Raymonzut 7a23117328 Remove unneeded paragraph title 2020-09-26 18:22:07 +02:00
Raymonzut bd3782cd93 Resize post entries to h2 with no horizontal margin 2020-08-21 21:49:54 +02:00
33 changed files with 227 additions and 1667 deletions
-2
View File
@@ -1,4 +1,2 @@
**/node_modules
**/dist
.git .git
.gitignore .gitignore
-22
View File
@@ -1,22 +0,0 @@
## Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Dependency directories
node_modules/
# Build
dist/
+30
View File
@@ -0,0 +1,30 @@
# Personal-Website
A nice introduction to my chaos
## Backstory
Instead of calling it my Personal-Website, you could better call it my Personal-Playground.
This repo is an example of not being afraid to change a codebase around.
It shows the journey of achieving code that fits the needs.
The posts started with pure excitement from building this site.
Making my own content and having complete contol over the format thrilled me.
For me, it is about making memorable content, something I can proudly look back on.
Sarcastic, serious and suprising content.
Which becomes super silly stuff when looking back at it ;)
## Content
Once in a while you can enjoy a new post on the website.
The posts started talking about the website and all changes that came along.
As the website is quite far in development, more focus is put into making relaxing content to read through.
## Experimentations
- Swapping QA page for a podcast page
- Adding a code++ page: page with a collection of helpful code improvements I came across
- Adding a portfolio
## Thank you
Thanks for reading all of this!
If you have any questions or sugestions, feel free to send an e-mail to me@raymon.dev.
-1
View File
@@ -1 +0,0 @@
**/node_modules
+1
View File
@@ -0,0 +1 @@
posts/*
-1
View File
@@ -1,4 +1,3 @@
FROM nginx as production-stage FROM nginx as production-stage
RUN mkdir /app RUN mkdir /app
COPY /public /app
COPY nginx.conf /etc/nginx/nginx.conf COPY nginx.conf /etc/nginx/nginx.conf
+72
View File
@@ -0,0 +1,72 @@
# Templates will be filled by posts
index_template = File.read!("./templates/post_index_page.html")
post_template = File.read!("./templates/post_single_page.html")
post_feed_item_template = File.read!("./templates/post_item.xml")
post_feed_template = File.read!("./templates/posts.xml")
# Converting handlebars in template to values
fill_template = fn template, pairs ->
Regex.compile!("{{(.*)}}") |>
Regex.replace(template, fn _, key -> if key != "content" do Map.get(pairs, String.to_atom(key)) else
Map.get(pairs, String.to_atom(key))
# Converting \n to paragraphs
|> String.split("\\n")
|> Enum.reject(fn(x) -> x == "" end)
|> Enum.map(fn paragraph -> ("<p>" <> paragraph <> "</p>\n") end)
|> Enum.join("")
end
end)
end
fill_template_pretemplated = fn template -> &fill_template.(template, &1) end
post_contents = File.ls!("./posts")
|> Enum.reject(fn(x) -> String.starts_with?(x, ".") end)
|> Enum.map(fn f -> File.read!("./posts/" <> f) end)
|> Enum.map(fn c -> String.split(c, "\n") end)
|> Enum.map(fn c -> Enum.reject(c, fn(x) -> x == "" end) end)
|> Enum.map(fn c -> Enum.chunk_every(c, 2) end)
|> Enum.map(fn c ->
Enum.map(c, fn [k, v] -> %{String.to_atom(k) => v} end)
|> Enum.reduce(%{}, fn(x, acc) -> Map.merge(x, acc) end)
end)
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
index_file = post_contents
|> Enum.sort_by(fn m -> Map.get(m, :date) |> (fn d -> Enum.find_index(months, &(&1 == String.slice(d, 8..10))) end).() end)
|> Enum.reverse()
# Group by month
|> Enum.group_by(fn m -> Map.get(m, :date) |> String.slice(8..15) end)
|> Enum.sort_by(fn {d, _c} -> ((length months) * elem(Integer.parse(String.slice(d, 4..7)), 0)) + Enum.find_index(months, &(&1 == String.slice(d, 0..2))) end)
|> Enum.reverse()
|> Enum.map(fn {month, posts} -> "\n<h1>" <> month <> "</h1>\n" <> (posts |>
Enum.map(fn post -> "<h2>" <> (Map.get(post, :date) |> String.slice(0..6)) <>
" - <a href=\"" <> Map.get(post, :id) <> ".html\">" <> Map.get(post, :title) <> "</a></h2>"
end) |> Enum.join("\n")) end)
|> Enum.join("\n")
|> (fn v -> %{index: v} end).()
|> fill_template_pretemplated.(index_template).()
File.open!("./public/gen/index.html", [:write])
|> IO.binwrite(index_file)
|> File.close()
post_contents
|> Enum.each(fn post ->
File.open!("./public/gen/" <> Map.get(post, :id) <> ".html", [:write])
|> IO.binwrite(fill_template.(post_template, post))
|> File.close()
end)
post_feed = post_contents
|> Enum.sort_by(fn m -> Map.get(m, :date) end)
|> Enum.reverse()
|> Enum.map(fill_template_pretemplated.(post_feed_item_template))
|> Enum.join("\n")
|> (fn v -> %{items: v} end).()
|> fill_template_pretemplated.(post_feed_template).()
File.open!("./public/gen/rss.xml", [:write])
|> IO.binwrite(post_feed)
|> File.close()
+2 -10
View File
@@ -7,9 +7,6 @@ events {
} }
http { http {
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
types {
application/javascript mjs;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" ' '$status $body_bytes_sent "$http_referer" '
@@ -29,13 +26,8 @@ http {
alias /app/assets/styling; alias /app/assets/styling;
} }
location /js { location /posts {
alias /app/lib; alias /app/gen/;
}
location /api {
proxy_pass https://raymon.dev/api;
proxy_buffering on;
} }
error_page 404 /404.html; error_page 404 /404.html;
+8
View File
@@ -0,0 +1,8 @@
id
4739537f3d29e1047a45c254
title
Example post for my new post page!
date
Mon, 11 May 2020 13:25:00 GMT
content
My new posts page is live
+1
View File
@@ -0,0 +1 @@
gen/*
+9
View File
@@ -21,6 +21,15 @@ nav {
font-style: italic; font-style: italic;
} }
h1 {
font-size: 2.0em;
}
h2 {
font-size: 1.25em;
margin: auto 0;
}
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {
--primary-color: #ffffeb; --primary-color: #ffffeb;
View File
+2 -6
View File
@@ -8,8 +8,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Raymon Zutekouw</title> <title>Raymon Zutekouw</title>
<link rel="preload" href="./lib/me.mjs" as="script" crossorigin="anonymous" type="application/javascript">
<link rel="preload" href="index.mjs" as="script" crossorigin="anonymous" type="application/javascript">
<link rel="dns-prefetch" href="https://cdn.statically.io"> <link rel="dns-prefetch" href="https://cdn.statically.io">
<link defer <link defer
@@ -17,7 +15,7 @@
href="https://cdn.statically.io/gh/dragonprojects/dragondesign/master/main.min.css" href="https://cdn.statically.io/gh/dragonprojects/dragondesign/master/main.min.css"
media="all" media="all"
> >
<link defer rel="stylesheet" href="css/general.css" media="all"> <link defer rel="stylesheet" href="/css/general.css" media="all">
</head> </head>
<body> <body>
@@ -30,10 +28,9 @@
<h1>Home</h1> <h1>Home</h1>
<div> <div>
<h2>Who am I?</h2>
<p> <p>
Hi there, good to see you on my website. Hi there, good to see you on my website.
My name is Raymon Zutekouw (<span id="age">born on 29-08-2002</span>) My name is Raymon Zutekouw; Self-thaught programmer by heart.
</p> </p>
<p> <p>
@@ -49,6 +46,5 @@
</p> </p>
</div> </div>
</div> </div>
<script async type="module" src="index.mjs"></script>
</body> </body>
</html> </html>
-4
View File
@@ -1,4 +0,0 @@
import { age } from "./lib/me.mjs"
const age_span = document.getElementById("age")
age_span.textContent = age().toString()
-17
View File
@@ -1,17 +0,0 @@
export function age() {
let birthdate = new Date(2002, 8, 29)
let now = new Date()
let age = now.getFullYear() - birthdate.getFullYear()
if (now.getMonth() < birthdate.getMonth()) {
age--
}
if (
birthdate.getMonth() === now.getMonth() &&
now.getDate() < birthdate.getDate()
) {
age--
}
return age
}
-24
View File
@@ -1,24 +0,0 @@
export async function getPosts(id) {
const BASE_URL = 'https://raymon.dev'
const BASE_ENDPOINT = '/api/posts'
const URL = BASE_URL + BASE_ENDPOINT + (id ? `/${id}` : '?sort=-1')
let posts = []
return await fetch(URL)
.then(res => res.json())
.then(res => {
if (id !== undefined) {
if (res === undefined) {
throw Error("Response body empty")
}
return [res]
}
else {
return res
}
})
.catch(err => {
console.log(`Error: ${err}`)
})
}
-42
View File
@@ -1,42 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta
name="description"
content="Raymon typing nonsense on his blog">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Raymon Zutekouw</title>
<link rel="preload" href="./lib/remote.mjs" as="script" crossorigin="anonymous" type="application/javascript">
<link rel="preload" href="posts.mjs" as="script" crossorigin="anonymous" type="application/javascript">
<link defer
rel="stylesheet"
href="https://cdn.statically.io/gh/dragonprojects/dragondesign/master/main.min.css"
media="all"
>
<link defer rel="stylesheet" href="css/general.css" media="all">
</head>
<body>
<noscript>
<p>
This page isn't as fancy without JavaScript.
Some parts will not load, but this will be minimal.
</p>
</noscript>
<div id="app">
<nav>
<a href="/">Home</a>|
<a href="/posts">Posts</a>|
<a href="/qa">QA</a>
</nav>
<h3>For those using a RSS reader, subscribe here: <a href="/api/posts/rss.xml">rss.xml</a></h3>
<!-- Placeholder for posts -->
<div id="posts"></div>
<script async type="module" src="posts.mjs"></script>
</div>
</body>
</html>
-61
View File
@@ -1,61 +0,0 @@
import { getPosts } from "./lib/remote.mjs"
function toMonthYearString(str) {
const date = new Date(str)
return `${date.toLocaleString('default', { month: 'long' })} ${date.getFullYear()}`
}
async function showPost(id) {
const post_list = await getPosts(id)
const post = post_list[0]
const title = document.createElement("h1")
title.textContent = post.title
const postsDOM = document.getElementById("posts")
postsDOM.appendChild(title)
post.content.split('\n').forEach(paragraph => {
const p = document.createElement("p")
p.innerHTML = paragraph
postsDOM.appendChild(p)
});
}
async function updatePosts() {
const posts = await getPosts()
const months = posts.map(post => toMonthYearString(post.date))
const uniques = Array.from(new Set(posts.map(post => toMonthYearString(post.date))))
const month_lists = uniques.map(month =>
posts.filter(
post => toMonthYearString(post.date) === month
)
)
const postsDOM = document.getElementById("posts")
uniques.forEach((month, i) => {
const month_DOM = document.createElement("h1")
month_DOM.textContent = month
month_lists[i].forEach((post, i) => {
const post_DOM = document.createElement("h6")
post_DOM.textContent = `${post.date.substring(0, 10)} - `
const post_link = document.createElement("a")
post_link.href = 'posts?post=' + post._id
post_link.textContent = post.title
post_DOM.appendChild(post_link)
month_DOM.appendChild(post_DOM)
})
postsDOM.appendChild(month_DOM)
})
}
// Check if a specific post is requested
let url = new URL(document.location.href);
url.searchParams.sort();
let post_id = url.searchParams.values().next().value;
const reg = /([0-9]|[a-f]){24}/
if (post_id && reg.test(post_id)) showPost(post_id)
else updatePosts()
+1 -1
View File
@@ -13,7 +13,7 @@
href="https://cdn.statically.io/gh/dragonprojects/dragondesign/master/main.min.css" href="https://cdn.statically.io/gh/dragonprojects/dragondesign/master/main.min.css"
media="all" media="all"
> >
<link defer rel="stylesheet" href="css/general.css" media="all"> <link defer rel="stylesheet" href="/css/general.css" media="all">
</head> </head>
<body> <body>
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta
name="description"
content="Raymon typing nonsense on his blog">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Raymon Zutekouw</title>
<link defer
rel="stylesheet"
href="https://cdn.statically.io/gh/dragonprojects/dragondesign/master/main.min.css"
media="all"
>
<link defer rel="stylesheet" href="/css/general.css" media="all">
</head>
<body>
<main>
<nav>
<a href="/">Home</a>|
<a href="/posts">Posts</a>|
<a href="/qa">QA</a>
</nav>
<header>
<h1>Post listing</h1>
</header>
{{index}}
</main>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
<item>
<title>{{title}}</title>
<link>https://raymon.dev/posts/{{id}}.html</link>
<guid>https://raymon.dev/posts/{{id}}.html</guid>
<description>
<![CDATA[
{{content}}
]]>
</description>
<content type="html">
{{content}}
</content>
<pubDate>{{date}}</pubDate>
</item>
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta
name="description"
content="Raymon typing nonsense on his blog">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Raymon Zutekouw</title>
<link defer
rel="stylesheet"
href="https://cdn.statically.io/gh/dragonprojects/dragondesign/master/main.min.css"
media="all"
>
<link defer rel="stylesheet" href="/css/general.css" media="all">
</head>
<body>
<main>
<nav>
<a href="/">Home</a>|
<a href="/posts">Posts</a>|
<a href="/qa">QA</a>
</nav>
<article>
<header>
<h1>{{title}}</h1>
<h3>For those using a RSS reader, subscribe here: <a href="/posts/rss.xml">rss.xml</a></h3>
</header>
<time datetime="{{date}}"></time>
{{content}}
</article>
</main>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="https://raymon.dev/api/posts/rss.xml" rel="self" type="application/rss+xml" />
<title>Posts</title>
<link>https://raymon.dev</link>
<description>Personal blog</description>
<language>en-us</language>
{{items}}
</channel>
</rss>
+8
View File
@@ -0,0 +1,8 @@
version: "3.3"
services:
client:
build: client/
ports:
- "5010:80"
volumes:
- "./client/public:/app:rw"
-3
View File
@@ -1,3 +0,0 @@
version: '3'
services:
-2
View File
@@ -1,2 +0,0 @@
**/node_modules
**/dist
-13
View File
@@ -1,13 +0,0 @@
FROM node:latest
WORKDIR /usr/src/server
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD [ "node", "index.js" ]
EXPOSE 5000
-23
View File
@@ -1,23 +0,0 @@
const fastify = require('fastify')()
fastify.register(require('fastify-cors'))
const posts = require('./routes/api/posts')
// Assuming they are all get requests
for (let [route, resolver] of posts.entries()) {
fastify.get('/posts' + route, resolver)
}
const port = process.env.PORT || 5000
const start = async () => {
try {
await fastify.listen(port, '0.0.0.0')
console.log(`Launched on port ${port} 🚀`)
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
}
start()
-1315
View File
File diff suppressed because it is too large Load Diff
-20
View File
@@ -1,20 +0,0 @@
{
"name": "server",
"version": "1.3.0",
"description": "The API for the website",
"main": "index.js",
"scripts": {
"start": "node ./index.js",
"dev": "nodemon server/index.js"
},
"keywords": [],
"author": "Raymonzut",
"license": "ISC",
"dependencies": {
"fastify": "^2.15.1",
"fastify-cors": "^3.0.3"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}
-1
View File
@@ -1 +0,0 @@
*.json
-6
View File
@@ -1,6 +0,0 @@
All the posts will be in here seperate in JSON format
post
- title
- date
- content
-93
View File
@@ -1,93 +0,0 @@
const fs = require('fs')
const posts_dir = 'posts/'
const routes = new Map();
if (!fs.existsSync(posts_dir)) throw Error(`Missing ${posts_dir}`)
let posts = readPosts();
setInterval(() => posts = readPosts(), 1000 * 60 * 60)
function readPosts() {
console.warn("reading all posts")
const files = fs.readdirSync(posts_dir)
if (files.length === 0) throw Error(`Could not find posts in ${posts_dir}`)
return files.filter(file_name => file_name.endsWith('.json'))
.map(file_name => `${posts_dir}${file_name}`)
.map(readJSONAsObject)
}
function readJSONAsObject(filename) {
return JSON.parse(fs.readFileSync(filename, 'utf8'))
}
function notFoundResponse(res) {
res.status(404).send('Sorry, can not find that')
}
routes.set('', async (req, res) => {
if (req.query.sort === '-1' || req.query.sort === '1') {
const posts_sorted = posts.sort((a, b) => {
return new Date(a.date).getTime() - new Date(b.date).getTime()
})
if (req.query.sort === '1') res.send(posts_sorted)
else res.send(posts_sorted.reverse())
return
}
// Default response when there are no interesting queries
res.send(posts)
})
routes.set('/:id', async (req, res) => {
const re = /[0-9A-Fa-f]{24}/g
if (!re.test(req.params.id)) {
notFoundResponse(res)
return
}
const results = posts.filter(post => post._id === req.params.id)
if (!results.length) notFoundResponse(res)
else res.send(results[0])
})
// Dynamic RSS feed
routes.set('/rss.xml', async (req, res) => {
const re = /(\/api\/)?(.*)\/rss/g
const result = [...req.raw.originalUrl.matchAll(re)]
if (!result) {
notFoundResponse(res)
return
}
const endpoint = result [1] ? result[1][2] : result[0][2]
// Assumes https
const BASE_URL = 'https://' + req.raw.hostname + endpoint
const ITEMS = posts.map(post =>
`\r\n <item>
<title>${post.title}</title>
<link>https://${req.raw.hostname}/posts?post=${post._id}</link>
<guid>https://${req.raw.hostname}/posts?post=${post._id}</guid>
<description>${post.content}</description>
</item>`
).join("\n")
const xml = `<?xml version="1.0" encoding="UTF-8" ?>\n<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="https://${req.raw.hostname}/api/posts/rss.xml" rel="self" type="application/rss+xml" />
<title>Posts</title>
<link>https://${req.raw.hostname}</link>
<description>Personal blog</description>
<language>en-us</language>
${ITEMS}
</channel>\n</rss>`
res.header('Content-Type', 'application/xml')
res.status(200).send(xml)
})
module.exports = routes