using webp for blogs now
125
scripts/convert-blog
Executable file
@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
import os
|
||||
import gzip
|
||||
import json
|
||||
import shutil
|
||||
import argparse
|
||||
import tempfile
|
||||
import subprocess
|
||||
from datetime import date
|
||||
from lxml import etree as ET
|
||||
|
||||
def open_blog(path: str) -> ET._Element:
|
||||
with gzip.open(path, 'rt', encoding='utf-8') as f:
|
||||
raw_svg = f.read()
|
||||
|
||||
return ET.fromstring(raw_svg)
|
||||
|
||||
def remove_by_xpath(el: ET._Element, xpath: str):
|
||||
for node in el.xpath(xpath):
|
||||
parent = node.getparent()
|
||||
parent.remove(node)
|
||||
|
||||
def tag_xpath(tag: str):
|
||||
return f".//*[local-name()='{tag}']"
|
||||
|
||||
def class_xpath(class_: str):
|
||||
return f".//*[contains(concat(' ', normalize-space(@class), ' '), '{class_}')]"
|
||||
|
||||
def save_blog(dir: str, root: ET._Element):
|
||||
os.makedirs(dir, exist_ok=True)
|
||||
|
||||
frame: ET._Element = root.xpath(tag_xpath('svg'))[0]
|
||||
width = int(frame.attrib['width'][:-2])
|
||||
height = int(frame.attrib['height'][:-2])
|
||||
root.attrib['viewBox'] = f'0 0 {width} {height}'
|
||||
|
||||
content: ET._Element = frame.getchildren()[0]
|
||||
root.remove(frame)
|
||||
root.append(content)
|
||||
|
||||
remove_by_xpath(root, ".//*[@id='write-document']")
|
||||
remove_by_xpath(root, ".//*[@id='write-defs']")
|
||||
remove_by_xpath(root, ".//*[@id='write-doc-background']")
|
||||
remove_by_xpath(root, class_xpath('ruleline'))
|
||||
|
||||
bookmarks: list[tuple[str, float]] = []
|
||||
for node in root.xpath(class_xpath('bookmark')):
|
||||
match = re.search(r"translate\([^\d\-]*([-+]?\d*\.?\d+),\s*([-+]?\d*\.?\d+)\)", node.attrib['transform'])
|
||||
bookmarks.append((node.attrib['id'], float(match.group(2))))
|
||||
node.getparent().remove(node)
|
||||
|
||||
hyperrefs: list[tuple[ET._Element, str, tuple[float, float], tuple[float, float]]] = []
|
||||
for node in root.xpath(class_xpath('hyperref')):
|
||||
anchor = node.xpath(tag_xpath('a'))[0]
|
||||
rect = anchor.xpath(tag_xpath('rect'))[0]
|
||||
x, y = float(rect.attrib['x']), float(rect.attrib['y'])
|
||||
w, h = float(rect.attrib['width']), float(rect.attrib['height'])
|
||||
url = anchor.attrib['{http://www.w3.org/1999/xlink}href']
|
||||
hyperrefs.append((node, url, (x, y), (w, h)))
|
||||
anchor.getparent().remove(anchor)
|
||||
node.getparent().remove(node)
|
||||
|
||||
tempdir = tempfile.mkdtemp()
|
||||
|
||||
try:
|
||||
with open(f'{tempdir}/main.svg', 'wb') as f:
|
||||
f.write(ET.tostring(root, xml_declaration=True, encoding="utf-8"))
|
||||
|
||||
subprocess.call(['magick', '-density', '300', '-background', 'none', f'{tempdir}/main.svg', f'{tempdir}/main.png'])
|
||||
subprocess.call(['magick', f'{tempdir}/main.png', '-crop', 'x16383', '+repage', f'{tempdir}/main_%03d.webp'])
|
||||
|
||||
main_files: list[str] = []
|
||||
for file in os.listdir(tempdir):
|
||||
if file.endswith('webp'): main_files.append(file)
|
||||
|
||||
url_defs: list[tuple[str, str, tuple[float, float], tuple[float, float]]] = []
|
||||
for node, url, (x, y), (w, h) in hyperrefs:
|
||||
name = os.urandom(15).hex()[:7]
|
||||
|
||||
svg = ET.Element('svg', attrib={'viewBox': f'{x} {y} {w} {h}'}, nsmap={None: 'http://www.w3.org/2000/svg'})
|
||||
svg.append(node)
|
||||
|
||||
with open(f'{tempdir}/{name}.svg', 'wb') as f:
|
||||
f.write(ET.tostring(svg, xml_declaration=True, encoding="utf-8"))
|
||||
|
||||
subprocess.call(['magick', '-density', '300', '-background', 'none', f'{tempdir}/{name}.svg',f'{tempdir}/{name}.png'])
|
||||
subprocess.call(['magick', f'{tempdir}/{name}.png', f'{tempdir}/{name}.webp'])
|
||||
|
||||
url_defs.append((f'{name}.webp', url, (x, y), (w, h)))
|
||||
|
||||
|
||||
for file in os.listdir(tempdir):
|
||||
if file.endswith('webp'):
|
||||
shutil.move(f'{tempdir}/{file}', f'{dir}/{file}')
|
||||
|
||||
with open(f'{dir}/_definition.json', 'w') as f:
|
||||
json.dump({
|
||||
'date': date.today().strftime("%Y-%m-%d"),
|
||||
'title': 'My Cool Blog Entry',
|
||||
'keywords': ['Cool', 'Blog'],
|
||||
'bookmarks': list(map(lambda b: {'id': b[0], 'offset': b[1]}, bookmarks)),
|
||||
'urls': list(map(lambda d: {'src': d[0], 'href': d[1], 'offset': d[2], 'dimensions': d[3]}, url_defs)),
|
||||
'files': sorted(main_files),
|
||||
'dimensions': [width, height],
|
||||
}, f, indent=4)
|
||||
|
||||
finally:
|
||||
shutil.rmtree(tempdir, ignore_errors=True)
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('--svgz', required=True, type=str)
|
||||
parser.add_argument('--dir', required=True, type=str)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
save_blog(args.dir, open_blog(args.svgz))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -31,7 +31,7 @@ RewriteRule ^(typo|admin|login|dashboard).*$ /dummy/kaffee.php [L]
|
||||
# Rewrite /lib/* or /blog/* to /error.php?code=404
|
||||
# with 404 response code (internal rewriting)
|
||||
# ----------------------------------------------------
|
||||
RewriteCond %{REQUEST_URI} ^/(lib|blog)(/.*)?$
|
||||
RewriteCond %{REQUEST_URI} ^/(lib)(/.*)?$
|
||||
RewriteRule ^ - [R=404,L]
|
||||
ErrorDocument 404 /error.php?code=404
|
||||
|
||||
|
106
www/blog.php
@ -2,22 +2,25 @@
|
||||
|
||||
require_once __DIR__ . '/lib/_index.php';
|
||||
|
||||
$id = (int) $_GET['id'];
|
||||
$id = $_GET['id'];
|
||||
|
||||
$blog = Blog::get($id);
|
||||
if ($blog === null) {
|
||||
http_response_code(404);
|
||||
require __DIR__ . '/404.php';
|
||||
$_GET['code'] = 404;
|
||||
require __DIR__ . '/error.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$svgs = $blog->getSVGs();
|
||||
|
||||
$rand_imprint = mt_rand() % 6;
|
||||
$rand_privacy = mt_rand() % 6;
|
||||
$rand_comments = mt_rand() % 6;
|
||||
$rand_send_comment = mt_rand() % 5;
|
||||
|
||||
function is_external_url(string $url): bool
|
||||
{
|
||||
return !str_starts_with($url, '#');
|
||||
}
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
@ -27,11 +30,9 @@ $rand_send_comment = mt_rand() % 5;
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.svg" type="image/x-icon">
|
||||
|
||||
<base target="_blank">
|
||||
<meta name="keywords" content="ludwig lehnert,blog,<?= join(",", $blog->keywords()) ?>">
|
||||
|
||||
<meta name="keywords" content="ludwig lehnert,blog,<?= join(",", $blog->keywords) ?>">
|
||||
|
||||
<title><?= $blog->title ?></title>
|
||||
<title><?= $blog->title() ?></title>
|
||||
|
||||
<style>
|
||||
html,
|
||||
@ -40,6 +41,11 @@ $rand_send_comment = mt_rand() % 5;
|
||||
background: white;
|
||||
}
|
||||
|
||||
body {
|
||||
width: min(100%, 800px);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -47,22 +53,47 @@ $rand_send_comment = mt_rand() % 5;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio:
|
||||
<?= $blog->width() ?>
|
||||
/
|
||||
<?= $blog->height() ?>
|
||||
;
|
||||
}
|
||||
|
||||
main>* {
|
||||
.content img {
|
||||
display: block;
|
||||
width: min(100vw, 1000px);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
svg .hyperref {
|
||||
cursor: pointer;
|
||||
.bookmarks {
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
svg .hyperref:hover {
|
||||
.bookmarks * {
|
||||
position: absolute;
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.urls {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.urls a {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.urls a:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@ -79,12 +110,12 @@ $rand_send_comment = mt_rand() % 5;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bottom-links {
|
||||
.legal-links {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.bottom-links svg {
|
||||
.legal-links svg {
|
||||
max-height: 4rem;
|
||||
height: 10vw;
|
||||
min-height: 2rem;
|
||||
@ -96,24 +127,47 @@ $rand_send_comment = mt_rand() % 5;
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<?php foreach ($svgs as $svg): ?>
|
||||
<?= $svg ?>
|
||||
<?php endforeach; ?>
|
||||
<div class="content">
|
||||
<?php foreach ($blog->files() as $file): ?>
|
||||
<img src="/blog/<?= $blog->id ?>/<?= $file ?>">
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="bookmarks">
|
||||
<?php foreach ($blog->bookmarks() as $bm): ?>
|
||||
<div id="<?= $bm['id'] ?>" style="top: <?= 100 * $bm['offset'] / $blog->height() ?>%;"></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="urls">
|
||||
<?php foreach ($blog->urls() as $url): ?>
|
||||
<a href="<?= $url['href'] ?>" <?php if (is_external_url($url['href']))
|
||||
echo 'target="_blank"'; ?> style="
|
||||
top: <?= 100 * $url['offset'][1] / $blog->height() ?>%;
|
||||
left: <?= 100 * $url['offset'][0] / $blog->width() ?>%;
|
||||
width: <?= 100 * $url['dimensions'][0] / $blog->width() ?>%;">
|
||||
<img src="/blog/<?= $blog->id ?>/<?= $url['src'] ?>" style="width: 100%;">
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div style="height: 100px"></div>
|
||||
|
||||
<footer>
|
||||
<div class="send-comment">
|
||||
<?= get_svg(__DIR__ . "/assets/blog/send-comment$rand_send_comment.svg") ?>
|
||||
</div>
|
||||
|
||||
<div style="height: 100px"></div>
|
||||
|
||||
<div class="bottom-links">
|
||||
<div class="legal-links">
|
||||
<?= get_svg(__DIR__ . "/assets/blog/imprint$rand_imprint.svg") ?>
|
||||
<?= get_svg(__DIR__ . "/assets/blog/privacy$rand_privacy.svg") ?>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div style="height: 100px"></div>
|
||||
</main>
|
||||
|
||||
<div style="height: 100px"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"title": "Test: First Blog Entry",
|
||||
"keywords": [
|
||||
"test",
|
||||
"first blog entry"
|
||||
],
|
||||
"date": "2025-04-15",
|
||||
"files": [
|
||||
"0.svg"
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 1.1 MiB |
@ -1,12 +0,0 @@
|
||||
{
|
||||
"title": "Thoughts about edge computation",
|
||||
"keywords": [
|
||||
"edge",
|
||||
"edge computation",
|
||||
"thoughts about edge computation"
|
||||
],
|
||||
"date": "2025-04-17",
|
||||
"files": [
|
||||
"1.svg"
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 9.3 MiB |
BIN
www/blog/one/07a08c4.webp
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
www/blog/one/09eef40.webp
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
www/blog/one/0aea930.webp
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
www/blog/one/15704c1.webp
Normal file
After Width: | Height: | Size: 678 B |
BIN
www/blog/one/17e65d5.webp
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
www/blog/one/1cc4eb8.webp
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
www/blog/one/2618a28.webp
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
www/blog/one/2f7fe30.webp
Normal file
After Width: | Height: | Size: 608 B |
BIN
www/blog/one/30d5de3.webp
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
www/blog/one/35ca008.webp
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
www/blog/one/4d55914.webp
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
www/blog/one/4e13630.webp
Normal file
After Width: | Height: | Size: 598 B |
BIN
www/blog/one/53145df.webp
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
www/blog/one/590f333.webp
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
www/blog/one/5ad1430.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
www/blog/one/64a799d.webp
Normal file
After Width: | Height: | Size: 606 B |
BIN
www/blog/one/717c078.webp
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
www/blog/one/7e7a7e4.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
www/blog/one/908a399.webp
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
www/blog/one/9c229c9.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
417
www/blog/one/_definition.json
Normal file
@ -0,0 +1,417 @@
|
||||
{
|
||||
"date": "2025-04-17",
|
||||
"title": "Thoughts about edge computation",
|
||||
"keywords": [
|
||||
"edge",
|
||||
"edge computation",
|
||||
"thoughts about"
|
||||
],
|
||||
"bookmarks": [
|
||||
{
|
||||
"id": "b-d5I21c9U7J",
|
||||
"offset": 1914.461
|
||||
},
|
||||
{
|
||||
"id": "b-fLRN0SS32U",
|
||||
"offset": 4834.461
|
||||
},
|
||||
{
|
||||
"id": "b-sXQ0BxXCpj",
|
||||
"offset": 5754.461
|
||||
},
|
||||
{
|
||||
"id": "b-yFt7MQSghl",
|
||||
"offset": 6014.461
|
||||
},
|
||||
{
|
||||
"id": "b-yR35Qi4Msf",
|
||||
"offset": 6774.461
|
||||
},
|
||||
{
|
||||
"id": "b-qxKx5OdrIT",
|
||||
"offset": 7954.461
|
||||
}
|
||||
],
|
||||
"urls": [
|
||||
{
|
||||
"src": "5ad1430.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Edge_computing",
|
||||
"offset": [
|
||||
291.575,
|
||||
369.178
|
||||
],
|
||||
"dimensions": [
|
||||
22.265,
|
||||
17.722
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "908a399.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Time_to_live",
|
||||
"offset": [
|
||||
163.033,
|
||||
1462.429
|
||||
],
|
||||
"dimensions": [
|
||||
49.831,
|
||||
17.907
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "b285770.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Active_users",
|
||||
"offset": [
|
||||
20.623,
|
||||
1579.52
|
||||
],
|
||||
"dimensions": [
|
||||
42.045,
|
||||
18.93
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "9c229c9.webp",
|
||||
"href": "#b-d5I21c9U7J",
|
||||
"offset": [
|
||||
323.993,
|
||||
2981.505
|
||||
],
|
||||
"dimensions": [
|
||||
75.638,
|
||||
30.079
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "717c078.webp",
|
||||
"href": "https://cloud.google.com/products/calculator",
|
||||
"offset": [
|
||||
180.869,
|
||||
3060.241
|
||||
],
|
||||
"dimensions": [
|
||||
184.16,
|
||||
28.221
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "b55185f.webp",
|
||||
"href": "https://cloud.google.com/run/pricing#pricing_for_regions_in_tier_1",
|
||||
"offset": [
|
||||
302.069,
|
||||
3384.668
|
||||
],
|
||||
"dimensions": [
|
||||
40.145,
|
||||
12.978
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "c5aea0a.webp",
|
||||
"href": "https://cloud.google.com/run/pricing#pricing_for_regions_in_tier_2",
|
||||
"offset": [
|
||||
394.121,
|
||||
3406.002
|
||||
],
|
||||
"dimensions": [
|
||||
45.343,
|
||||
13.91
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "fe86e6f.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Virtual_private_server",
|
||||
"offset": [
|
||||
65.769,
|
||||
3762.201
|
||||
],
|
||||
"dimensions": [
|
||||
33.444,
|
||||
18.997
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "53145df.webp",
|
||||
"href": "https://www.hetzner.com/cloud/",
|
||||
"offset": [
|
||||
153.962,
|
||||
3760.823
|
||||
],
|
||||
"dimensions": [
|
||||
127.856,
|
||||
20.199
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "17e65d5.webp",
|
||||
"href": "#b-fLRN0SS32U",
|
||||
"offset": [
|
||||
328.668,
|
||||
5220.784
|
||||
],
|
||||
"dimensions": [
|
||||
99.508,
|
||||
30.447
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "a0be263.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/JSON_Web_Token",
|
||||
"offset": [
|
||||
414.148,
|
||||
5500.374
|
||||
],
|
||||
"dimensions": [
|
||||
39.779,
|
||||
26.594
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "30d5de3.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Round-trip_delay",
|
||||
"offset": [
|
||||
78.417,
|
||||
6561.832
|
||||
],
|
||||
"dimensions": [
|
||||
40.835,
|
||||
16.013
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "2f7fe30.webp",
|
||||
"href": "#b-sXQ0BxXCpj",
|
||||
"offset": [
|
||||
132.554,
|
||||
6562.702
|
||||
],
|
||||
"dimensions": [
|
||||
9.351,
|
||||
14.592
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "4e13630.webp",
|
||||
"href": "#b-yFt7MQSghl",
|
||||
"offset": [
|
||||
227.85,
|
||||
6564.368
|
||||
],
|
||||
"dimensions": [
|
||||
13.333,
|
||||
9.814
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "c1e1de3.webp",
|
||||
"href": "#b-sXQ0BxXCpj",
|
||||
"offset": [
|
||||
108.014,
|
||||
6621.458
|
||||
],
|
||||
"dimensions": [
|
||||
10.852,
|
||||
14.101
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "09eef40.webp",
|
||||
"href": "#b-fLRN0SS32U",
|
||||
"offset": [
|
||||
359.605,
|
||||
7101.638
|
||||
],
|
||||
"dimensions": [
|
||||
88.394,
|
||||
29.214
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "15704c1.webp",
|
||||
"href": "#b-sXQ0BxXCpj",
|
||||
"offset": [
|
||||
397.962,
|
||||
7261.618
|
||||
],
|
||||
"dimensions": [
|
||||
8.913,
|
||||
18.387
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "f4b2484.webp",
|
||||
"href": "#b-yFt7MQSghl",
|
||||
"offset": [
|
||||
440.001,
|
||||
7261.85
|
||||
],
|
||||
"dimensions": [
|
||||
14.139,
|
||||
17.629
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "f44e23c.webp",
|
||||
"href": "#b-sXQ0BxXCpj",
|
||||
"offset": [
|
||||
182.096,
|
||||
7583.017
|
||||
],
|
||||
"dimensions": [
|
||||
10.694,
|
||||
13.19
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "64a799d.webp",
|
||||
"href": "#b-yFt7MQSghl",
|
||||
"offset": [
|
||||
278.685,
|
||||
7586.278
|
||||
],
|
||||
"dimensions": [
|
||||
14.377,
|
||||
10.387
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "ea08577.webp",
|
||||
"href": "#b-sXQ0BxXCpj",
|
||||
"offset": [
|
||||
109.759,
|
||||
7643.622
|
||||
],
|
||||
"dimensions": [
|
||||
9.297,
|
||||
13.103
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "0aea930.webp",
|
||||
"href": "#b-fLRN0SS32U",
|
||||
"offset": [
|
||||
123.703,
|
||||
7736.656
|
||||
],
|
||||
"dimensions": [
|
||||
115.125,
|
||||
34.642
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "7e7a7e4.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/ACID",
|
||||
"offset": [
|
||||
84.38,
|
||||
8640.77
|
||||
],
|
||||
"dimensions": [
|
||||
48.438,
|
||||
20.131
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "07a08c4.webp",
|
||||
"href": "#b-fLRN0SS32U",
|
||||
"offset": [
|
||||
361.351,
|
||||
8940.184
|
||||
],
|
||||
"dimensions": [
|
||||
91.42,
|
||||
33.02
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "35ca008.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/NoSQL",
|
||||
"offset": [
|
||||
216.592,
|
||||
9041.812
|
||||
],
|
||||
"dimensions": [
|
||||
68.499,
|
||||
18.973
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "1cc4eb8.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Relational_database",
|
||||
"offset": [
|
||||
381.531,
|
||||
8540.439
|
||||
],
|
||||
"dimensions": [
|
||||
77.199,
|
||||
20.197
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "590f333.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Graph_database",
|
||||
"offset": [
|
||||
307.123,
|
||||
9079.422
|
||||
],
|
||||
"dimensions": [
|
||||
51.318,
|
||||
26.761
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "4d55914.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Content_delivery_network",
|
||||
"offset": [
|
||||
87.833,
|
||||
9358.795
|
||||
],
|
||||
"dimensions": [
|
||||
250.808,
|
||||
30.198
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "f072857.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Denial-of-service_attack#Distributed_DoS",
|
||||
"offset": [
|
||||
82.307,
|
||||
9398.894
|
||||
],
|
||||
"dimensions": [
|
||||
54.456,
|
||||
19.93
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "f63dffe.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/TLS_termination_proxy",
|
||||
"offset": [
|
||||
80.339,
|
||||
9479.985
|
||||
],
|
||||
"dimensions": [
|
||||
163.634,
|
||||
19.53
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "2618a28.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/Load_balancing_(computing)",
|
||||
"offset": [
|
||||
169.377,
|
||||
9440.959
|
||||
],
|
||||
"dimensions": [
|
||||
155.393,
|
||||
32.558
|
||||
]
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"main_000.webp",
|
||||
"main_001.webp"
|
||||
],
|
||||
"dimensions": [
|
||||
480,
|
||||
9700
|
||||
]
|
||||
}
|
BIN
www/blog/one/a0be263.webp
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
www/blog/one/b285770.webp
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
www/blog/one/b55185f.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
www/blog/one/c1e1de3.webp
Normal file
After Width: | Height: | Size: 696 B |
BIN
www/blog/one/c5aea0a.webp
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
www/blog/one/ea08577.webp
Normal file
After Width: | Height: | Size: 536 B |
BIN
www/blog/one/f072857.webp
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
www/blog/one/f44e23c.webp
Normal file
After Width: | Height: | Size: 638 B |
BIN
www/blog/one/f4b2484.webp
Normal file
After Width: | Height: | Size: 912 B |
BIN
www/blog/one/f63dffe.webp
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
www/blog/one/fe86e6f.webp
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
www/blog/one/main_000.webp
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
www/blog/one/main_001.webp
Normal file
After Width: | Height: | Size: 963 KiB |
BIN
www/blog/zero/18bddb5.webp
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
www/blog/zero/2db2951.webp
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
www/blog/zero/60d1274.webp
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
www/blog/zero/6a1748c.webp
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
www/blog/zero/76c4bca.webp
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
www/blog/zero/913fe45.webp
Normal file
After Width: | Height: | Size: 11 KiB |
90
www/blog/zero/_definition.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"date": "2025-04-15",
|
||||
"title": "Test: First Blog Entry",
|
||||
"keywords": [
|
||||
"test",
|
||||
"first blog entry"
|
||||
],
|
||||
"bookmarks": [],
|
||||
"urls": [
|
||||
{
|
||||
"src": "18bddb5.webp",
|
||||
"href": "https://google.com",
|
||||
"offset": [
|
||||
176.315,
|
||||
378.853
|
||||
],
|
||||
"dimensions": [
|
||||
42.536,
|
||||
25.653
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "76c4bca.webp",
|
||||
"href": "https://handwritten.blog",
|
||||
"offset": [
|
||||
271.101,
|
||||
511.231
|
||||
],
|
||||
"dimensions": [
|
||||
142.634,
|
||||
40.839
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "913fe45.webp",
|
||||
"href": "mailto:blog@lehnert.dev",
|
||||
"offset": [
|
||||
60.28,
|
||||
868.451
|
||||
],
|
||||
"dimensions": [
|
||||
188.266,
|
||||
44.067
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "60d1274.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/SVG",
|
||||
"offset": [
|
||||
454.485,
|
||||
1012.028
|
||||
],
|
||||
"dimensions": [
|
||||
35.147,
|
||||
20.576
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "6a1748c.webp",
|
||||
"href": "https://en.wikipedia.org/wiki/PNG",
|
||||
"offset": [
|
||||
320.493,
|
||||
1057.862
|
||||
],
|
||||
"dimensions": [
|
||||
44.903,
|
||||
19.773
|
||||
]
|
||||
},
|
||||
{
|
||||
"src": "2db2951.webp",
|
||||
"href": "https://inkscape.org/",
|
||||
"offset": [
|
||||
64.774,
|
||||
1231.579
|
||||
],
|
||||
"dimensions": [
|
||||
87.11,
|
||||
37.658
|
||||
]
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"main_000.webp"
|
||||
],
|
||||
"dimensions": [
|
||||
650,
|
||||
1400
|
||||
]
|
||||
}
|
BIN
www/blog/zero/main_000.webp
Normal file
After Width: | Height: | Size: 344 KiB |
@ -8,6 +8,8 @@ if (isset($_GET['code'])) {
|
||||
$code = (int) $_GET['code'];
|
||||
}
|
||||
|
||||
http_response_code($code);
|
||||
|
||||
$msg = '🪐 Unknown error! Something went wrong in the fabric of spacetime.';
|
||||
$action = 'Stabilize systems';
|
||||
|
||||
|
@ -136,6 +136,10 @@ usort($blogs, function ($a, $b) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@keyframes glow {
|
||||
from {
|
||||
@ -188,15 +192,20 @@ usort($blogs, function ($a, $b) {
|
||||
</div>
|
||||
|
||||
<div class="blog-entries">
|
||||
<h2 class="none">Handwritten Blog</h2>
|
||||
|
||||
<?php foreach ($blogs as $blog): ?>
|
||||
<div class="blog-entry">
|
||||
<h2><a href="/blog/<?= $blog->id ?>"><?= $blog->title ?></a></h2>
|
||||
<h2><a href="/blog/<?= $blog->id ?>"><?= $blog->title() ?></a></h2>
|
||||
<p><?= $blog->formatDate() ?></p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div style="height: 350px;"></div>
|
||||
|
||||
<!-- SEO Tags -->
|
||||
<h1 class="none">Ludwig Lehnert</h1>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
|
@ -3,41 +3,68 @@
|
||||
class Blog
|
||||
{
|
||||
public readonly string $id;
|
||||
public readonly string $title;
|
||||
public readonly array $keywords;
|
||||
public readonly string $date;
|
||||
public readonly array $files;
|
||||
public readonly array $json;
|
||||
|
||||
public function __construct(string $id, string $title, array $keywords, string $date, array $files)
|
||||
public function __construct(string $id, array $json)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->title = $title;
|
||||
$this->keywords = $keywords;
|
||||
$this->date = $date;
|
||||
$this->files = $files;
|
||||
$this->json = $json;
|
||||
}
|
||||
|
||||
public function date(): string
|
||||
{
|
||||
return $this->json['date'];
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return $this->json['title'];
|
||||
}
|
||||
|
||||
public function keywords(): array
|
||||
{
|
||||
return $this->json['keywords'];
|
||||
}
|
||||
|
||||
public function bookmarks(): array
|
||||
{
|
||||
return $this->json['bookmarks'];
|
||||
}
|
||||
|
||||
public function urls(): array
|
||||
{
|
||||
return $this->json['urls'];
|
||||
}
|
||||
|
||||
public function files(): array
|
||||
{
|
||||
return $this->json['files'];
|
||||
}
|
||||
|
||||
public function width(): float
|
||||
{
|
||||
return $this->json['dimensions'][0];
|
||||
}
|
||||
|
||||
public function height(): float
|
||||
{
|
||||
return $this->json['dimensions'][1];
|
||||
}
|
||||
|
||||
public function formatDate(): string
|
||||
{
|
||||
return date("F jS, Y", strtotime($this->date));
|
||||
return date("F jS, Y", strtotime($this->date()));
|
||||
}
|
||||
|
||||
public function getSVGs(): array
|
||||
public static function get(string $id): Blog|null
|
||||
{
|
||||
return array_map(function ($file) {
|
||||
return get_svg(__DIR__ . '/../blog/' . $file);
|
||||
}, $this->files);
|
||||
}
|
||||
|
||||
public static function get(string|int $id): Blog|null
|
||||
{
|
||||
$path = __DIR__ . "/../blog/$id.json";
|
||||
$path = __DIR__ . "/../blog/$id/_definition.json";
|
||||
if (!file_exists($path))
|
||||
return null;
|
||||
|
||||
try {
|
||||
$json = json_decode(file_get_contents($path), true);
|
||||
return new Blog($id, $json['title'], $json['keywords'], $json['date'], $json['files']);
|
||||
return new Blog($id, $json);
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
@ -48,18 +75,12 @@ class Blog
|
||||
*/
|
||||
public static function getAll()
|
||||
{
|
||||
$files = scandir(__DIR__ . '/../blog');
|
||||
$files = array_filter($files, function ($file) {
|
||||
return str_ends_with($file, '.json');
|
||||
});
|
||||
|
||||
$ids = array_map(function ($file) {
|
||||
return str_replace('.json', '', $file);
|
||||
}, $files);
|
||||
$dirs = scandir(__DIR__ . '/../blog');
|
||||
$dirs = array_filter($dirs, fn($dir) => is_dir(__DIR__ . "/../blog/$dir") && $dir !== '.' && $dir !== '..');
|
||||
|
||||
$blogs = [];
|
||||
|
||||
foreach ($ids as $id) {
|
||||
foreach ($dirs as $id) {
|
||||
$blog = Blog::get($id);
|
||||
if ($blog !== null)
|
||||
array_push($blogs, $blog);
|
||||
|
@ -4,6 +4,8 @@ require_once __DIR__ . "/lib/_index.php";
|
||||
|
||||
$blogs = Blog::getAll();
|
||||
|
||||
header("Content-Type: application/xml");
|
||||
|
||||
echo '<?';
|
||||
?>xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
@ -18,20 +20,20 @@ echo '<?';
|
||||
<?php foreach ($blogs as $blog): ?>
|
||||
<url>
|
||||
<loc>https://lehnert.dev/blog/<?= $blog->id ?></loc>
|
||||
<lastmod><?= $blog->date ?></lastmod>
|
||||
<!-- <lastmod><?= $blog->date() ?></lastmod> -->
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<url>
|
||||
<loc>https://lehnert.dev/imprint</loc>
|
||||
<loc>https://lehnert.dev/imprint/</loc>
|
||||
<lastmod>2025-04-16</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://lehnert.dev/privacy</loc>
|
||||
<loc>https://lehnert.dev/privacy/</loc>
|
||||
<lastmod>2025-04-16</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
|