3 Commits
Author SHA1 Message Date
jake 363497d354 Add Plausible 2026-04-05 20:46:41 +01:00
jake b97c9ea3ee First implementation of comments 2026-02-19 12:58:42 +00:00
jake 82def98e7e First implementation of comments 2026-02-19 12:46:43 +00:00
20 changed files with 152 additions and 316 deletions
+1 -2
View File
@@ -1,4 +1,3 @@
__pycache__
.vscode
.buildinfo.json
*.md
.buildinfo.json
Vendored
-1
View File
@@ -148,7 +148,6 @@ pipeline {
stage('Clear cache') {
steps{
sh "/var/lib/jenkins/clearCFCache/clearCache.py a514fb61e1413b88aabbb19df16b8508"
sh "/var/lib/jenkins/clearCFCache/clearCache.py 2e68842d0d5f5512f319e16d73899b9d"
}
}
}
-3
View File
@@ -439,9 +439,6 @@ Alias "/static" "/var/www/jc/static/"
</Directory>
Alias "/robots.txt" "/var/www/jc/static/robots.txt"
Alias "/.well-known/security.txt" "/var/www/jc/static/security.txt"
Alias "/llms.txt" "/var/www/jc/static/llms.txt"
<Files jc.wsgi>
Require all granted
+2 -2
View File
@@ -6,7 +6,7 @@ set -x -o pipefail
content_dir=""
if [[ -d "../jc-content" ]]; then
content_dir="-v $(realpath ../jc-content):/var/www/jc/projects:z"
content_dir="-v $(realpath ../jc-content):/var/www/jc/projects"
fi
docker run -e DISCORD_ERR_HOOK=dummy $1 $content_dir jc-ng-localtest
docker run -e DISCORD_ERR_HOOK=dummy $1 -v $(pwd)/src/:/var/www/jc $content_dir jc-ng-localtest
+6 -26
View File
@@ -16,35 +16,21 @@ from .content import ContentArea
from .contact import ContactForm
from .storage import LocalStorage
from .links import Links
from .comments import *
app = Flask(__name__)
md_path = path.join(path.realpath(path.dirname(__file__)), path.normpath('../projects/'))
projects = ContentArea(
directory=LocalStorage(path.join(md_path, 'projects')),
root_templ='projects.html',
post_templ='article.html',
directory=LocalStorage(md_path),
name='projects',
title='Projects',
desc='A selection of projects I\'ve been involved in',
import_name=__name__
)
blog = ContentArea(
directory=LocalStorage(path.join(md_path, 'blog')),
root_templ='blog.html',
post_templ='post.html',
name='blog',
title='Blog',
desc='Less formal writing, usually something I\'ve been working on that isn\'t worthy of a project write up',
import_name=__name__
)
import_name=__name__)
app.register_blueprint(projects, url_prefix='/projects')
app.register_blueprint(blog, url_prefix='/blog')
app.register_blueprint(ContactForm('contact', __name__), url_prefix='/contact')
app.register_blueprint(Links(path.join(md_path, 'links.json'), 'links', __name__), url_prefix='/links')
app.register_blueprint(Approval(path.join(projects.md_directory.uri, 'comments.db'), 'comments', __name__), url_prefix='/comments')
class DiscordLogger(logging.Handler):
''' Simple logging handler to send a message to Discord '''
@@ -88,13 +74,7 @@ def inject_branding() -> dict:
case _:
brand = req_domain
return {'branding': brand, 'url': req_domain}
@app.context_processor
def inject_globals():
return {
'canonical_url': f"https://jakec.tech{request.path}"
}
return {'branding': brand}
@app.route('/')
def index() -> str:
@@ -162,7 +142,7 @@ def sitemap():
url = ET.SubElement(root, 'url')
ET.SubElement(url, 'loc').text = base_url + route
ET.SubElement(url, 'lastmod').text = date
for article in projects.get_all_posts():
for article in projects.get_live_posts():
if 'link' in article.metadata:
continue
url = ET.SubElement(root, 'url')
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/python3
import sqlite3
from flask import Blueprint, Response, request
from uuid import uuid4
from requests import post
from os import environ
class PostComments():
def __init__(self, post_id: str, db_path: str):
self.__db_path = db_path
self.__post_id = post_id
self._webhook = environ['DISCORD_WEBHOOK']
with sqlite3.connect(db_path) as db:
cursor = db.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS comments (name TEXT, comment TEXT, date INT, post_id TEXT, approved BOOL, key TEXT)")
self.comments = cursor.execute(
"SELECT name, comment, date FROM comments WHERE approved = 1 AND post_id = ? ORDER BY date DESC",
(post_id,)
).fetchall()
def send_to_discord(self, name: str, comment: str, comment_id: int, key: str):
''' Send the message '''
message_to_send = f'New comment from {name}\n\n{comment}'
if len(message_to_send) > 2000:
chars_to_lose = len(message_to_send) - 1900
message_to_send = message_to_send[-chars_to_lose:]
message_to_send += f'\n\n[Approve](https://jakecharman.co.uk/comments/approve/{comment_id}?key={key})'
post(self._webhook, data={'content': message_to_send}, timeout=30)
def make_comment(self, name: str, comment: str):
with sqlite3.connect(self.__db_path) as db:
key = str(uuid4())
cursor = db.cursor()
cursor.execute(
"INSERT INTO comments (name, comment, date, post_id, approved, key) VALUES (?, ?, datetime('now'), ?, 0, ?)",
(name, comment, self.__post_id, key)
)
db.commit()
self.send_to_discord(name, comment, cursor.lastrowid, key)
return cursor.lastrowid
class Approval(Blueprint):
def __init__(self, db_path: str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__db_path = db_path
self.add_url_rule('/approve/<comment_id>', view_func=self.approve)
def approve(self, comment_id: str):
with sqlite3.connect(self.__db_path) as db:
cursor = db.cursor()
key = cursor.execute("SELECT key FROM comments WHERE rowid = ?", (comment_id,)).fetchone()[0]
if request.args.get('key') == key:
with sqlite3.connect(self.__db_path) as db:
cursor = db.cursor()
cursor.execute("UPDATE comments SET approved = 1 WHERE rowid = ?", (comment_id,))
db.commit()
return Response(status=200)
return Response(status=403)
+27 -20
View File
@@ -5,17 +5,18 @@ import json
from datetime import datetime
import frontmatter
from markdown import markdown
import readtime
from bs4 import BeautifulSoup
from flask import render_template, Response, Blueprint
from flask import render_template, Response, Blueprint, request, redirect
from .storage import LocalStorage
from .comments import PostComments, Approval
class ContentArea(Blueprint):
def __init__(self, directory: LocalStorage, root_templ: str, post_templ: str, title: str, desc: str, *args, **kwargs):
def __init__(self, directory: LocalStorage, *args, **kwargs):
self.md_directory = directory
super().__init__(*args, **kwargs)
self.add_app_template_filter(self.category_title, 'category_title')
self.add_app_template_filter(self.human_date, 'human_date')
self.add_app_template_filter(self.to_html, 'to_html')
@@ -24,11 +25,7 @@ class ContentArea(Blueprint):
self.add_url_rule('/', view_func=self.projects)
self.add_url_rule('/category/<category_id>/', view_func=self.category)
self.add_url_rule('/<article_id>', view_func=self.article)
self.root_templ = root_templ
self.post_templ = post_templ
self.title = title
self.desc = desc
self.add_url_rule('/<article_id>/comment', view_func=self.comment, methods=['POST'])
def processor(self) -> dict:
''' Jninja processors '''
@@ -37,14 +34,14 @@ class ContentArea(Blueprint):
post_soup = BeautifulSoup(html, 'html.parser')
all_text = ' '.join([x.get_text() for x in post_soup.findAll('p')])
return ' '.join(all_text.split()[:200])
return dict(get_excerpt=get_excerpt)
def get_readtime(post: str) -> str:
return readtime.of_html(post).text
def get_readtime_mins(post: str) -> int:
return readtime.of_html(post).minutes
def category_title(self, category_id: str) -> str:
''' Jninja filter to get a category title by its ID '''
with self.md_directory.open('categories.json') as categories_file:
categories = json.load(categories_file)
return dict(get_excerpt=get_excerpt, get_readtime=get_readtime, get_readtime_mins=get_readtime_mins)
return categories.get(category_id).get('title', '')
def human_date(self, iso_date: str) -> str:
''' Jninja filter to convert an ISO date to human readable. '''
@@ -94,12 +91,12 @@ class ContentArea(Blueprint):
error='There\'s nothing here... yet.',
description='I\'m still working on this page. Check back soon for some content.')
return render_template(self.root_templ,
return render_template('projects.html',
articles=articles_to_return,
all_categories=categories,
title=self.title,
title='Projects',
page_title='Projects - ',
description=self.desc)
description='A selection of projects I\'ve been involved in')
def category(self, category_id: str) -> str:
''' Load the page for a given category '''
@@ -127,7 +124,7 @@ class ContentArea(Blueprint):
error='There\'s nothing here... yet.',
description='I\'m still working on this page. Check back soon for some content.')
return render_template(self.root_templ, articles=articles_to_return,
return render_template('projects.html', articles=articles_to_return,
title=the_category['title'],
description=the_category['long_description'],
page_title=f'{the_category["title"]} - ',
@@ -144,6 +141,16 @@ class ContentArea(Blueprint):
return Response(status=500)
the_article = articles[0]
return render_template(self.post_templ, post=markdown(the_article.content),
comments = PostComments(the_article.metadata['id'], path.join(self.md_directory.uri, 'comments.db')).comments
return render_template('article.html', post=markdown(the_article.content),
metadata=the_article.metadata,
page_title=f'{the_article.metadata["title"]} - ')
comments = comments,
page_title=f'{the_article.metadata["title"]} - ')
def comment(self, article_id: str):
PostComments(article_id, path.join(self.md_directory.uri, 'comments.db')).make_comment(
request.form['name'],
request.form['comment']
)
return redirect(f'/projects/{article_id}?comment=true#comments')
+20 -28
View File
@@ -1,5 +1,5 @@
{% extends 'main.html' %}
{% block description %}{{ metadata.description }}{% endblock %}
{% block head %}
{% if metadata.gallery %}
<script>
@@ -11,42 +11,34 @@
});
</script>
{% endif %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": {{ metadata.title | tojson }},
"datePublished": "{{ metadata.date }}",
"image": "https://jakec.tech/image/{{ metadata.image }}",
"timeRequired": "PT{{ get_readtime_mins(post) }}M",
"author": {
"@type": "Person",
"name": "Jake Charman",
"url": "https://jakec.tech/"
},
"mainEntityOfPage": "https://jakec.tech/projects/{{ metadata.id }}"
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Projects", "item": "https://jakec.tech/projects/"},
{"@type": "ListItem", "position": 2, "name": {{ metadata.title | tojson }}, "item": "https://jakec.tech/projects/{{ metadata.id }}"}
]
}
</script>
{% endblock %}
{% block content %}
<main>
<section id="article">
<h1>{{ metadata.title}} </h1>
<p>{{ get_readtime(post) }} read</p>
<p>{{ metadata.date | human_date }}</p>
<hr />
{{post|safe}}
</section>
</main>
<section id="comments">
<h2>{{comments | length}} Comment{% if comments | length != 1 %}s{% endif %}</h2>
{% for comment in comments %}
<div class="comment">
<strong>{{ comment[0] }} - {{ comment[2] | human_date }}</strong>
<p>{{ comment[1] }}</p>
</div>
{% endfor %}
<h3>Leave a comment</h3>
{% if request.args.get('comment') is none %}
<form action="./{{metadata.id}}/comment" method="post">
<input type="text", name="name" placeholder="Name" required>
<textarea name="comment" placeholder="Comment" required></textarea>
<input type="submit" value="Submit">
</form>
{% else %}
<p>Thank you! Your comment will appear once it is approved</p>
{% endif %}
</section>
{% endblock %}
-58
View File
@@ -1,58 +0,0 @@
{% extends 'main.html' %}
{% block content %}
<main id="blog-main">
<nav id="filter">
<label for="filter_category">Filter:</label>
<select id="filter_category" name="filter_category" onchange="update_filter()">
<option value="all">All Categories</option>
{% for category in all_categories %}
<option {{ 'selected' if current_category==category }} value="{{ category }}">
{{all_categories.get(category).get('title')}}</option>
{% endfor %}
</select>
</nav>
<h2>{{ title }}</h2>
<p>{{ description }}</p>
{% for row in articles %}
<div class="blog-post">
{% if row.get('link') is not none %}
<a href="{{ row.link }}">
{% else %}
<a href="/blog/{{ row.id }}">
{% endif %}
<img class="blogpost-thumb"
alt="{{ row.alt }}"
srcset="
{% for i in range(200, 5100, 100) %}
/image/{{ row.image }}?w={{i}} {{i}}w{{"," if not loop.last}}
{% endfor %}
"
sizes="
(max-width: 999px) 80vw,
(min-width: 1000px) 20vw
"
src="/image/{{ row.image }}">
</a>
<div class="blogpost-text">
{% if row.get('link') is not none %}
<a href="{{ row.link }}">
{% else %}
<a href="/blog/{{ row.id }}">
{% endif %}
<h3>{{ row.title }}</h3>
</a>
<p class="blogpost-date">{{ row.date | human_date }}</p>
<p>{{ get_readtime(row.content) }} read</p>
<hr />
<p class="blogpost-description">{{ get_excerpt(row) }}... <a href="/blog/{{ row.id }}">Read more</a></p>
<hr />
{% for category in row.categories %}
<a class="blogpost-category" href="/blog/category/{{ category }}"> {{ all_categories.get(category, {}).get('title', '') }}{{ ", " if not loop.last }}</loop></a>
{% endfor %}
</div>
</div>
{% endfor %}
</section>
</main>
{% endblock %}
+25 -30
View File
@@ -1,7 +1,5 @@
{% extends 'main.html' %}
{% block logo %}<a href='/'><h1>{{branding|upper}}</h1></a>{% endblock %}
{% block content %}
<main>
<section id="technology">
@@ -20,38 +18,35 @@
My personal projects can also be found over on the <a href="/projects/">projects</a> page. Be aware though that if I'm able to write about a project here, it most likely wasn't done professionally so some may be a little rough around the edges.
</p>
<div id="techlogos">
<a href="https://ansible.com"><img alt="Ansible Logo" src="/static/images/technology/ansible.png" /></a>
<a href="https://aws.amazon.com/"><img alt="AWS Logo" src="/static/images/technology/aws.png" /></a>
<a href="https://azure.microsoft.com/en-gb"><img alt="Azure Logo" src="/static/images/technology/azure.png" /></a>
<a href="https://dotnet.microsoft.com/en-us/languages/csharp"><img alt="C# Logo" src="/static/images/technology/csharp.png" /></a>
<a href="https://www.debian.org/"><img alt="Debian Logo" src="/static/images/technology/debian.png" /></a>
<a href="https://www.digitalocean.com/"><img alt="DigitalOcean Logo" src="/static/images/technology/digitalocean.png" /></a>
<a href="https://www.docker.com/"><img alt="Docker Logo" src="/static/images/technology/docker.png" /></a>
<a href="https://www.freeipa.org/"><img alt="FreeIPA Logo" src="/static/images/technology/freeipa.png" /></a>
<a href="https://cloud.google.com/"><img alt="Google Cloud Logo" src="/static/images/technology/gcloud.png" /></a>
<a href="https://git-scm.com/"><img alt="Git Logo" src="/static/images/technology/git.png" /></a>
<a href="https://www.grafana.com/"><img alt="Grafana Logo" src="/static/images/technology/grafana.png" /></a>
<a href="https://hadoop.apache.org/"><img alt="Hadoop Logo" src="/static/images/technology/hadoop.svg" /></a>
<a href="https://hive.apache.org/"><img alt="Hive Logo" src="/static/images/technology/hive.svg" /></a>
<a href="https://www.java.com/"><img alt="Java Logo" src="/static/images/technology/java.png" /></a>
<a href="https://www.jenkins.io/"><img alt="Jenkins Logo" src="/static/images/technology/jenkins.png" /></a>
<a href="https://mariadb.org/"><img alt="MariaDB Logo" src="/static/images/technology/mariadb.png" /></a>
<a href="https://nginx.org/"><img alt="NGINX Logo" src="/static/images/technology/nginx.svg" /></a>
<a href="https://www.proxmox.com/"><img alt="Proxmox Logo" src="/static/images/technology/proxmox.png" /></a>
<a href="https://www.python.org/"><img alt="Python Logo" src="/static/images/technology/python.png" /></a>
<a href="https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux"><img alt="RedHat Logo" src="/static/images/technology/redhat.png" /></a>
<a href="https://rockylinux.org/"><img alt="Rocky Linux Logo" src="/static/images/technology/rocky.png" /></a>
<a href="https://subversion.apache.org/"><img alt="Subversion Logo" src="/static/images/technology/svn.svg" /></a>
<a href="https://ansible.com"><img src="/static/images/technology/ansible.png" /></a>
<a href="https://aws.amazon.com/"><img src="/static/images/technology/aws.png" /></a>
<a href="https://azure.microsoft.com/en-gb"><img src="/static/images/technology/azure.png" /></a>
<a href="https://dotnet.microsoft.com/en-us/languages/csharp"><img src="/static/images/technology/csharp.png" /></a>
<a href="https://www.debian.org/"><img src="/static/images/technology/debian.png" /></a>
<a href="https://www.digitalocean.com/"><img src="/static/images/technology/digitalocean.png" /></a>
<a href="https://www.docker.com/"><img src="/static/images/technology/docker.png" /></a>
<a href="https://www.freeipa.org/"><img src="/static/images/technology/freeipa.png" /></a>
<a href="https://cloud.google.com/"><img src="/static/images/technology/gcloud.png" /></a>
<a href="https://git-scm.com/"><img src="/static/images/technology/git.png" /></a>
<a href="https://www.grafana.com/"><img src="/static/images/technology/grafana.png" /></a>
<a href="https://hadoop.apache.org/"><img src="/static/images/technology/hadoop.svg" /></a>
<a href="https://hive.apache.org/"><img src="/static/images/technology/hive.svg" /></a>
<a href="https://www.java.com/"><img src="/static/images/technology/java.png" /></a>
<a href="https://www.jenkins.io/"><img src="/static/images/technology/jenkins.png" /></a>
<a href="https://mariadb.org/"><img src="/static/images/technology/mariadb.png" /></a>
<a href="https://nginx.org/"><img src="/static/images/technology/nginx.svg" /></a>
<a href="https://www.proxmox.com/"><img src="/static/images/technology/proxmox.png" /></a>
<a href="https://www.python.org/"><img src="/static/images/technology/python.png" /></a>
<a href="https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux"><img src="/static/images/technology/redhat.png" /></a>
<a href="https://rockylinux.org/"><img src="/static/images/technology/rocky.png" /></a>
<a href="https://subversion.apache.org/"><img src="/static/images/technology/svn.svg" /></a>
</div>
<div id="certs">
<a href="https://catalog-education.oracle.com/ords/certview/sharebadge?id=A6C9B3D3D21627EB9C2504395194FAF772E01412911D4ADC78063133D54579ED"><img alt="Oracle Cloud certification badge" src="/static/images/certs/OCIF2023CA.png" /></a>
<a href="https://catalog-education.oracle.com/ords/certview/sharebadge?id=A6C9B3D3D21627EB9C2504395194FAF772E01412911D4ADC78063133D54579ED"><img src="/static/images/certs/OCIF2023CA.png" /></a>
</div>
<div class="social">
<a class="button" href="https://www.linkedin.com/in/jakecharman/"><i class="fa-brands fa-linkedin-in"></i></a>
<a class="button" href="https://github.com/jcharman/"><i class="fa-brands fa-github"></i></a>
{% if url == 'jakec.tech' %}
<a class="button" href="https://instagr.am/jakec.tech"><i class="fa-brands fa-instagram"></i></a>
{% endif %}
</div>
</div>
</div>
@@ -66,10 +61,10 @@
<hr />
<p>When I'm not at work, I can often be found at <a href="https://santapod.com">Santa Pod Raceway</a> with <a href="https://nitrojunkie.uk">Nitro Junkie Racing</a></p>
<p>We run a Top Fuel motorcycle purpose built for drag racing. The engine is loosely based on a Kawasaki Z 1000, but supercharged, and nitromethane injected to produce around 1,000 horsepower.</p>
<p>I've also had the opportunity to work on a 10,000 horsepower Top Fuel Dragster. I <a href="/projects/topfuel_dragster">wrote about this on the Nitro Junkie website, and later extended that post here.</a></p>
<p>I've also had the opportunity to work on a 10,000 horsepower Top Fuel Dragster. I <a href="https://jakecharman.co.uk/projects/topfuel_dragster">wrote about this on the Nitro Junkie website, and later extended that post here.</a></p>
<div class="gallery">
<iframe class="yt" src="https://www.youtube.com/embed/Wa-V8mQTXFk?si=W-LzfR3Qj_jgAdkY&amp;clip=UgkxV9lBj4pP1cvnR5seb82RQpWeE7RdnOXB&amp;clipt=EOrWtgsYyqu6Cw&amp;autoplay=1&amp;mute=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<img alt="Jake working on a Top Fuel Dragster in the pits at Santa Pod Raceway" src="/static/images/jake_tf_1.jpg">
<img src="/static/images/jake_tf_1.jpg">
</div>
<div class="social">
<a class="button" href="https://nitrojunkie.uk"><i class="fa-solid fa-globe"></i></a>
-1
View File
@@ -12,7 +12,6 @@
{% if link.get('img') is not none %}
<a href="{{link.src}}" target="_blank"></a>
<img class="link-thumb"
alt="{{ link.title }}"
srcset="
{% for i in range(200, 5100, 100) %}
/image/{{ link.img }}?w={{i}} {{i}}w{{"," if not loop.last}}
+2 -30
View File
@@ -1,11 +1,9 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta property="og:url" content="{{ canonical_url }}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{% block description %}Personal website of Jake Charman. A technology professional based in the UK.{% endblock %}">
<meta name="description" content="Personal website of Jake Charman. A technology professional based in the UK.">
<title>{{ page_title }}{{branding}}</title>
<link rel="canonical" href="{{ canonical_url }}">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&family=Tourney:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Orbitron:wght@400..900&family=Tourney:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style/mobile.css" />
@@ -33,29 +31,6 @@
gtag('config', 'G-6WMXXY0RL0');
</script>
{% if url == 'jakecharman.co.uk' %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Jake Charman",
"url": "https://jakecharman.co.uk/",
"jobTitle": "Technology Professional",
"sameAs": [
"https://www.linkedin.com/in/jakecharman/",
"https://github.com/jcharman/"
]
}
</script>
{% endif %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "{{ branding }}",
"url": "https://{{ url }}/"
}
</script>
{% block head %}{% endblock %}
</head>
<body>
@@ -63,15 +38,12 @@
<nav id="top-nav">
<a href="/">About</a>
<a href="/projects/">Projects</a>
{% if url != 'jakecharman.co.uk' %}
<a href="/blog">Blog</a>
{% endif %}
<a href="/contact/">Contact</a>
<a href="/links/">Links</a>
</nav>
<div id="logo-container">
<div id="logo">
{% block logo %}<a href='/'>{{branding|upper}}</a>{% endblock %}
<a href='/'><h1>{{branding|upper}}</h1></a>
</div>
</div>
</header>
-69
View File
@@ -1,69 +0,0 @@
{% extends 'main.html' %}
{% block description %}{{ metadata.description }}{% endblock %}
{% block head %}
{% if metadata.gallery %}
<script>
document.addEventListener("DOMContentLoaded",function(){
post_gallery = new Viewer(document.getElementById('article'), {
'url': 'realsrc',
'title': false
});
});
</script>
{% endif %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": {{ metadata.title | tojson }},
"description": {{ metadata.description | tojson }},
"timeRequired": "PT{{ get_readtime_mins(post) }}M",
"datePublished": "{{ metadata.date }}",
{% if metadata.updated %}
"dateModified": "{{ metadata.updated }}",
{% endif %}
"image": "https://jakec.tech/image/{{ metadata.image }}",
"author": {
"@type": "Person",
"name": "Jake Charman",
"url": "https://jakec.tech/"
},
"publisher": {
"@type": "Person",
"name": "Jake Charman",
"url": "https://jakec.tech/"
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://jakec.tech/blog/{{ metadata.id }}"
},
"url": "https://jakec.tech/blog/{{ metadata.id }}",
"inLanguage": "en-GB"
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Projects", "item": "https://jakec.tech/projects/"},
{"@type": "ListItem", "position": 2, "name": {{ metadata.title | tojson }}, "item": "https://jakec.tech/projects/{{ metadata.id }}"}
]
}
</script>
{% endblock %}
{% block content %}
<main>
<section id="article">
<h1>{{ metadata.title}} </h1>
<p>{{ get_readtime(post) }} read</p>
<p>{{ metadata.date | human_date }}</p>
{% if metadata.updated %}
<p>Updated: {{ metadata.updated | human_date }}</p>
{% endif %}
<hr />
{{post|safe}}
</section>
</main>
{% endblock %}
+1 -2
View File
@@ -24,7 +24,6 @@
<a href="/projects/{{ row.id }}">
{% endif %}
<img class="project-thumb"
alt="{{ row.alt }}"
srcset="
{% for i in range(200, 5100, 100) %}
/image/{{ row.image }}?w={{i}} {{i}}w{{"," if not loop.last}}
@@ -47,7 +46,7 @@
<p class="article-description">{{ row.description }}</p>
<p class="article-date">{{ row.date | human_date }}</p>
{% for category in row.categories %}
<a class="article-category" href="/projects/category/{{ category }}"> {{ all_categories.get(category, {}).get('title', '') }}{{ ", " if not loop.last }}</a>
<a class="article-category" href="/projects/category/{{ category }}">{{ category | category_title }}{{ ", " if not loop.last }}</loop></a>
{% endfor %}
</div>
</div>
-1
View File
@@ -7,4 +7,3 @@ python-frontmatter>=1.1.0
requests>=2.32.3
pillow>=11.0.0
mod_wsgi>=5.0.2
readtime>=3.0.0
+2 -3
View File
@@ -1,11 +1,10 @@
function update_filter() {
var section = window.location.pathname.split('/')[1]
var project_filter = document.getElementById("filter_category");
console.log(project_filter.value)
if (project_filter.value == 'all') {
window.location.href = '/'+section;
window.location.href = '/projects';
}
else {
window.location.href = '/'+section+'/category/' + project_filter.value;
window.location.href = '/projects/category/' + project_filter.value;
}
}
-10
View File
@@ -1,10 +0,0 @@
# Jake Charman
> Personal website of Jake Charman, a technology professional based in the UK,
> covering distributed systems work and motorsport (Nitro Junkie Top Fuel drag racing).
## Pages
- [About](https://jakecharman.co.uk/): Background, technology stack, and motorsport involvement.
- [Projects](https://jakecharman.co.uk/projects/): Write-ups of personal and professional projects.
- [Links](https://jakecharman.co.uk/links/): Curated external links.
- [Contact](https://jakecharman.co.uk/contact/): Contact form.
-3
View File
@@ -1,3 +0,0 @@
Contact: mailto:security@jakecharman.co.uk
Expires: 2036-01-01T00:00:00.000Z
Preferred-Languages: en
-5
View File
@@ -99,7 +99,6 @@
.yt {
width: calc(66% - 40px);
min-height: 100%;
height: auto;
}
.gallery {
@@ -129,8 +128,4 @@
padding: 10px;
height: 14vw;
}
.blogpost-thumb {
width: 12.5vw;
}
}
+6 -22
View File
@@ -45,16 +45,7 @@ footer{
#logo>a{
text-decoration: none;
font-family: "Orbitron", sans-serif;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-size: 2em;
}
#logo>a>h1{
font-size: 1em;
}
footer h2, section h2{
margin: 0;
@@ -339,20 +330,13 @@ pre{
object-fit: cover;
}
#blog-main {
padding: 0 10px 0 10px;
#comments {
border-top: 1px solid #e5e5e5;
}
.blog-post {
border: 5px solid #e5e5e5;
padding: 10px;
.comment {
background-color: #4c4c4c;
margin: 10px;
border-radius: 15px;
overflow: hidden;
}
.blogpost-thumb {
float: left;
padding-right: 20px;
width: 25vw;
padding: 5px;
border-radius: 10px;
}