Alter 21 files
Update `README.md` Update `__main__.py` Add `feeds.py` Update `process.py` Add `typedef.py` Update `util.py` Add `docs.md` Update `poetry.lock` Update `pyproject.toml` Add `content.md` Add `paralol.jpg` Update `the-modern-web-sucks.md` Update `config.yml` Update `head.html` Update `header.html` Update `base.html` Add `blog_index.html` Update `blog_post.html` Update `custom.css` Delete `house-palette.css` Update `typography.css`
This commit is contained in:
parent
e9601d0476
commit
53d678f5f2
21 changed files with 395 additions and 47 deletions
|
@ -1,4 +1,8 @@
|
|||
# website
|
||||
|
||||
* Blog code
|
||||
* Aside contents
|
||||
* Aside contents
|
||||
* Blog tags
|
||||
* Blog favourites
|
||||
* New button
|
||||
* Homepage
|
||||
* Callouts?
|
|
@ -4,14 +4,11 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|||
import fire
|
||||
from collections.abc import Callable
|
||||
import process
|
||||
import functools
|
||||
|
||||
|
||||
def make_absurl_filter(site_conf: any) -> Callable[[str], str]:
|
||||
def inner(value):
|
||||
bu = site_conf["baseURL"]
|
||||
return bu.rstrip("/") + "/" + value.lstrip("/")
|
||||
|
||||
return inner
|
||||
return functools.partial(get_absolute_url, site_conf)
|
||||
|
||||
|
||||
def run(base_dir: str, output_dir: str = "_dist"):
|
||||
|
@ -33,19 +30,20 @@ def run(base_dir: str, output_dir: str = "_dist"):
|
|||
)
|
||||
|
||||
jinja_env.filters["absurl"] = make_absurl_filter(site_config)
|
||||
jinja_env.filters["fmtdate"] = lambda x: x.strftime("%Y-%m-%d")
|
||||
|
||||
process.content(base_dir, output_dir, jinja_env, site_config)
|
||||
print()
|
||||
process.blog(base_dir, output_dir, jinja_env, site_config)
|
||||
|
||||
os.makedirs((d := output_dir / "blog" / "the-modern-web-sucks"))
|
||||
with open(d/"index.htm", "w") as f:
|
||||
f.write(jinja_env.get_template("_layouts/blog_post.html").render({"site": site_config, "post": (x := {"title": "The Modern Web Sucks", "description": "And what can be done about it"}), "page": x}))
|
||||
|
||||
thing_overrides = {"rendered": "template"}
|
||||
thing_overrides = {"rendered": "page"}
|
||||
res_parts = []
|
||||
for (key, count) in get_counts().items():
|
||||
s = "" if count == 1 else "s"
|
||||
res_parts.append(f"{key} {count} {thing_overrides.get(key, 'file')}{s}")
|
||||
|
||||
print()
|
||||
|
||||
rprint(INFO_LEADER + "Finished working, " + ", ".join(res_parts))
|
||||
|
||||
|
||||
|
|
82
builder/feeds.py
Normal file
82
builder/feeds.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
import xml.etree.ElementTree as ElementTree
|
||||
from typedef import *
|
||||
from util import *
|
||||
import datetime
|
||||
import json as libjson
|
||||
|
||||
|
||||
def _format_atom_date(date: datetime.date) -> str:
|
||||
return date_to_datetime(date).astimezone(datetime.timezone.utc).isoformat()
|
||||
|
||||
|
||||
def _get_feed_title(site_config: any) -> str:
|
||||
return f"{site_config['person']['nickname']}'s blog"
|
||||
|
||||
|
||||
def atom(site_config: any, posts: list[AbbreviatedPost]) -> str:
|
||||
root = ElementTree.Element("feed", xmlns="http://www.w3.org/2005/Atom")
|
||||
|
||||
blog_url = get_absolute_url(site_config, "/blog/")
|
||||
|
||||
ElementTree.SubElement(root, "title").text = _get_feed_title(site_config)
|
||||
ElementTree.SubElement(root, "id").text = blog_url
|
||||
ElementTree.SubElement(root, "updated").text = (
|
||||
_format_atom_date(
|
||||
max(
|
||||
[post.publishedDate for post in posts]
|
||||
+ [post.updatedDate for post in posts if post.updatedDate is not None]
|
||||
)
|
||||
)
|
||||
if len(posts) != 0
|
||||
else ""
|
||||
)
|
||||
ElementTree.SubElement(root, "link").text = blog_url
|
||||
ElementTree.SubElement(
|
||||
ElementTree.SubElement(root, "author"),
|
||||
"name",
|
||||
).text = f"{site_config['person']['firstName']} {site_config['person']['surname']}"
|
||||
|
||||
for post in posts:
|
||||
entry = ElementTree.SubElement(root, "entry")
|
||||
ElementTree.SubElement(entry, "title").text = post.title
|
||||
ElementTree.SubElement(entry, "updated").text = _format_atom_date(
|
||||
post.updatedDate if post.updatedDate is not None else post.publishedDate
|
||||
)
|
||||
ElementTree.SubElement(entry, "id").text = post.slug
|
||||
ElementTree.SubElement(
|
||||
entry, "link", href=get_absolute_url(site_config, f"/blog/{post.slug}/")
|
||||
)
|
||||
ElementTree.SubElement(entry, "summary", type="text").text = post.description
|
||||
|
||||
return ElementTree.tostring(root, xml_declaration=True, encoding="unicode")
|
||||
|
||||
|
||||
def json(site_config: any, posts: list[AbbreviatedPost]) -> str:
|
||||
root = {
|
||||
"version": "https://jsonfeed.org/version/1.1",
|
||||
"title": _get_feed_title(site_config),
|
||||
"home_page_url": get_absolute_url(site_config, "/blog/"),
|
||||
"feed_url": get_absolute_url(site_config, "/blog/feed.json"),
|
||||
"authors": [
|
||||
{
|
||||
"name": f"{site_config['person']['firstName']} {site_config['person']['surname']}"
|
||||
}
|
||||
],
|
||||
"items": [],
|
||||
}
|
||||
|
||||
for post in posts:
|
||||
x = {
|
||||
"id": post.slug,
|
||||
"url": get_absolute_url(site_config, f"/blog/{post.slug}/"),
|
||||
"title": post.title,
|
||||
"content_text": "",
|
||||
"date_published": _format_atom_date(post.publishedDate),
|
||||
}
|
||||
if post.description != "":
|
||||
x["summary"] = post.description
|
||||
if post.updatedDate is not None:
|
||||
x["date_modified"] = _format_atom_date(post.updatedDate)
|
||||
root["items"].append(x)
|
||||
|
||||
return libjson.dumps(root, indent="\t")
|
|
@ -3,6 +3,9 @@ from pathlib import Path
|
|||
from jinja2 import Environment
|
||||
import shutil
|
||||
from rich import print as rprint
|
||||
import mistune
|
||||
import feeds
|
||||
from typedef import *
|
||||
|
||||
|
||||
def _template_frontmatter(data: any, jinja_env: Environment, context: any):
|
||||
|
@ -20,7 +23,9 @@ class _FileType(enum.Enum):
|
|||
POST = enum.auto()
|
||||
|
||||
|
||||
def _walk_content(start_dir: str | Path) -> Generator[tuple[str, _FileType], None, None]:
|
||||
def _walk_content(
|
||||
start_dir: str | Path,
|
||||
) -> Generator[tuple[str, _FileType], None, None]:
|
||||
if type(start_dir) is not Path:
|
||||
start_dir = Path(start_dir)
|
||||
|
||||
|
@ -46,7 +51,8 @@ def _walk_content(start_dir: str | Path) -> Generator[tuple[str, _FileType], Non
|
|||
|
||||
|
||||
def content(base_dir: Path, output_dir: Path, jinja_env: Environment, site_config: any):
|
||||
for (fpath, filetype) in _walk_content((walk_dir := base_dir / "content")):
|
||||
walk_dir = base_dir / "content"
|
||||
for (fpath, filetype) in _walk_content(walk_dir):
|
||||
site_inner_path = fpath.relative_to(
|
||||
walk_dir
|
||||
) # the path of the file *inside* a site directory structure (eg. inside of `_dist` or inside of `content`)
|
||||
|
@ -58,7 +64,7 @@ def content(base_dir: Path, output_dir: Path, jinja_env: Environment, site_confi
|
|||
|
||||
render_as_directory = (
|
||||
site_inner_path.name.lower() != "index.html"
|
||||
and tpl_frontmatter.get("asDirectory", True)
|
||||
and bool(tpl_frontmatter.get("asDirectory", True))
|
||||
)
|
||||
target_path = output_dir / (
|
||||
site_inner_path
|
||||
|
@ -87,8 +93,149 @@ def content(base_dir: Path, output_dir: Path, jinja_env: Environment, site_confi
|
|||
update_counts("rendered", 1)
|
||||
case _:
|
||||
if filetype != _FileType.STATIC:
|
||||
rprint(WARN_LEADER + f"Treating [bold]{fpath}[/bold] (type {filetype.name}) as a static file")
|
||||
rprint(
|
||||
WARN_LEADER
|
||||
+ f"Treating [bold]{fpath}[/bold] (type {filetype.name}) as a static file"
|
||||
)
|
||||
target_path = output_dir / site_inner_path
|
||||
os.makedirs(target_path.parent, exist_ok=True)
|
||||
shutil.copy(fpath, target_path)
|
||||
update_counts("copied", 1)
|
||||
|
||||
|
||||
BLOG_DATE_FORMAT = "%Y-%m-%d"
|
||||
|
||||
|
||||
def blog(base_dir: Path, output_dir: Path, jinja_env: Environment, site_config: any):
|
||||
walk_dir = base_dir / "blog"
|
||||
posts = {}
|
||||
for (fpath, filetype) in _walk_content(walk_dir):
|
||||
inner_path = fpath.relative_to(walk_dir)
|
||||
|
||||
match filetype:
|
||||
case _FileType.POST:
|
||||
with open(fpath) as f:
|
||||
post_frontmatter, raw_post_md = extract_frontmatter(f.read())
|
||||
|
||||
post_slug = (
|
||||
fpath.name[:-3] if fpath.name != "content.md" else fpath.parent.name
|
||||
)
|
||||
|
||||
# check required keys
|
||||
missing_keys = [
|
||||
key
|
||||
for key in ["title", "publishedDate"]
|
||||
if key not in post_frontmatter
|
||||
]
|
||||
if len(missing_keys) > 0:
|
||||
rprint(
|
||||
ERROR_LEADER
|
||||
+ f"Post [bold]{post_slug}[/bold] missing the following frontmatter keys: "
|
||||
+ ",".join(missing_keys)
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
if post_slug in posts:
|
||||
rprint(
|
||||
ERROR_LEADER + f"Duplicate post slug [bold]{post_slug}[/bold]"
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
target_path = output_dir / "blog" / post_slug / "index.html"
|
||||
os.makedirs(target_path.parent, exist_ok=True)
|
||||
|
||||
rprint(
|
||||
INFO_LEADER
|
||||
+ f"Rendering [bold]{fpath.relative_to(base_dir)}[/bold] => [bold]{target_path}[/bold]"
|
||||
)
|
||||
|
||||
if "updatedDate" in post_frontmatter:
|
||||
post_frontmatter["updatedDate"] = list(
|
||||
sorted(post_frontmatter["updatedDate"], reverse=True)
|
||||
)
|
||||
|
||||
posts[post_slug] = post_frontmatter
|
||||
|
||||
# render markdown to html
|
||||
markdown = mistune.create_markdown(escape=False)
|
||||
rendered_html = markdown(raw_post_md)
|
||||
|
||||
# build jinja context
|
||||
ctx = {
|
||||
"site": site_config,
|
||||
"post": post_frontmatter,
|
||||
"content": rendered_html,
|
||||
"page": {
|
||||
k: post_frontmatter[k]
|
||||
for k in ["title", "description"]
|
||||
if k in post_frontmatter
|
||||
},
|
||||
}
|
||||
ctx["page"]["canonicalURL"] = f"/blog/{post_slug}/"
|
||||
|
||||
# execute jinja template
|
||||
tpl = jinja_env.get_template("_layouts/blog_post.html")
|
||||
res = tpl.render(ctx)
|
||||
|
||||
# dump to file
|
||||
with open(target_path, "w") as f:
|
||||
f.write(res)
|
||||
|
||||
update_counts("rendered", 1)
|
||||
case _:
|
||||
if filetype != _FileType.STATIC:
|
||||
rprint(
|
||||
WARN_LEADER
|
||||
+ f"Treating [bold]{fpath}[/bold] (type {filetype.name}) as a static file"
|
||||
)
|
||||
target_path = output_dir / "blog" / inner_path
|
||||
os.makedirs(target_path.parent, exist_ok=True)
|
||||
shutil.copy(fpath, target_path)
|
||||
update_counts("copied", 1)
|
||||
|
||||
# generate listing
|
||||
post_list = []
|
||||
for slug in posts:
|
||||
post = posts[slug]
|
||||
post_list.append(
|
||||
AbbreviatedPost(
|
||||
slug,
|
||||
post["title"],
|
||||
post.get("description", ""),
|
||||
post["publishedDate"],
|
||||
None
|
||||
if "updatedDate" not in post or len(post["updatedDate"]) == 0
|
||||
else post["updatedDate"][0],
|
||||
)
|
||||
)
|
||||
|
||||
post_list = list(sorted(post_list, key=lambda x: x.publishedDate))
|
||||
|
||||
with open(output_dir / "blog" / "index.html", "w") as f:
|
||||
tpl = jinja_env.get_template("_layouts/blog_index.html")
|
||||
r = tpl.render(
|
||||
{
|
||||
"site": site_config,
|
||||
"page": {
|
||||
"title": "Blog",
|
||||
"canonicalURL": "/blog/",
|
||||
},
|
||||
"posts": post_list,
|
||||
}
|
||||
)
|
||||
f.write(r)
|
||||
|
||||
update_counts("generated", 1)
|
||||
|
||||
# generate feeds
|
||||
with open(output_dir / "blog" / "feed.atom", "w") as f:
|
||||
f.write(
|
||||
feeds.atom(site_config, post_list)
|
||||
)
|
||||
|
||||
with open(output_dir / "blog" / "feed.json", "w") as f:
|
||||
f.write(
|
||||
feeds.json(site_config, post_list)
|
||||
)
|
||||
|
||||
update_counts("generated", 2)
|
||||
|
|
5
builder/typedef.py
Normal file
5
builder/typedef.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from collections import namedtuple
|
||||
|
||||
AbbreviatedPost = namedtuple(
|
||||
"AbbreviatedPost", ["slug", "title", "description", "publishedDate", "updatedDate"]
|
||||
)
|
|
@ -1,10 +1,11 @@
|
|||
import datetime
|
||||
import os
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
import enum
|
||||
from collections.abc import Generator
|
||||
|
||||
ERROR_LEADER = "[[red bold]ERR [/bold red] "
|
||||
ERROR_LEADER = "[[red bold]FAIL[/bold red]] "
|
||||
WARN_LEADER = "[[yellow bold]WARN[/bold yellow]] "
|
||||
INFO_LEADER = "[[turquoise2 bold]info[/turquoise2 bold]] "
|
||||
|
||||
|
@ -45,3 +46,12 @@ def extract_frontmatter(instr: str) -> tuple[any, str]:
|
|||
|
||||
frontmatter = load_yaml(raw_frontmatter.strip("-\n"))
|
||||
return frontmatter, trailer
|
||||
|
||||
|
||||
def get_absolute_url(site_config: any, path: str):
|
||||
bu = site_config["baseURL"]
|
||||
return bu.rstrip("/") + "/" + path.lstrip("/")
|
||||
|
||||
|
||||
def date_to_datetime(d: datetime.date) -> datetime.datetime:
|
||||
return datetime.datetime(d.year, d.month, d.day)
|
||||
|
|
56
docs.md
Normal file
56
docs.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# akpain.net documentation
|
||||
|
||||
## Content templating
|
||||
|
||||
The `site/content` directory will be walked with any files and directories starting with `_` being ignored.
|
||||
|
||||
`*.html` files will be templated; other files will be copied directly to the output.
|
||||
|
||||
When being templated, files are executed with the following context:
|
||||
|
||||
```python
|
||||
{
|
||||
"site": "<parsed YAML content of the site/config.yml file>: any",
|
||||
"page": "<parsed YAML frontmatter of the template>: any",
|
||||
}
|
||||
```
|
||||
|
||||
Frontmatter is templated with the context of `{"site": "<as above>"}`.
|
||||
|
||||
Some frontmatter keys that are treated specially are:
|
||||
* `title`: the page title, used in the \<title> tag and metadata
|
||||
* `description`: the page description, used in the page's metadata
|
||||
* `canonicalURL`: the canonical URL of the page, used in the page's metadata
|
||||
* `imageURL`: the URL of an image to be used in the page's metadata
|
||||
* `asDirectory`: if the boolean value of this is `True`, this file will not be rendered as a directory.
|
||||
|
||||
Once rendered, a template called `/credits.html` will be rendered to `/credits/index.html` unless `asDirectory == True`.
|
||||
|
||||
## Blog content
|
||||
|
||||
The `site/blog` directory will be walked with any files and directories starting with `_` being ignored.
|
||||
|
||||
`*.md` files will be templated and rendered using the `site/content/_layouts/blog_post.html` template using the frontmatter below; other files will be copied directly to the output.
|
||||
|
||||
The filename of the Markdown file will be used as the slug of the post. If the filename is `content.md`, the name of the parent directory of the file will be used as the post slug.
|
||||
|
||||
```python
|
||||
{
|
||||
"site": "<parsed YAML content of the site/config.yml file>: any",
|
||||
"post": "<parsed YAML frontmatter of the post>: any",
|
||||
"content": "<rendered HTML content of the post>: str"
|
||||
}
|
||||
```
|
||||
|
||||
The frontmatter is not templated.
|
||||
|
||||
Some frontmatter keys that are treated specially are:
|
||||
* `title`: **required**, the post title
|
||||
* `description`: the post description
|
||||
* `imageURL`: the URL of an image to be used in the page's metadata
|
||||
* `publishedDate`: **required**, date in the form `YYYY-MM-DD`
|
||||
* `updatedDate`: a list of dates in the form `YYYY-MM-DD`
|
||||
* `hidden`: a boolean value, omits the post from the listing and feeds if true
|
||||
* `tags`: a list of tags that can be applied to a post
|
||||
|
||||
Any post with the special tag `favourite` is
|
14
poetry.lock
generated
14
poetry.lock
generated
|
@ -130,6 +130,18 @@ files = [
|
|||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mistune"
|
||||
version = "3.0.2"
|
||||
description = "A sane and fast Markdown parser with useful plugins and renderers"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"},
|
||||
{file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.17.2"
|
||||
|
@ -255,4 +267,4 @@ tests = ["pytest", "pytest-cov"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "8fe1345ad8aabb6289287c6ec7ad9fcc5c420086c9c437a204b5982b446aac1d"
|
||||
content-hash = "a89183bd8594d0aa4c78441a7c0307ac71a0195e657ddfb94a033a5fe41cb300"
|
||||
|
|
|
@ -11,6 +11,7 @@ Jinja2 = "^3.1.2"
|
|||
PyYAML = "^6.0.1"
|
||||
fire = "^0.5.0"
|
||||
rich = "^13.7.0"
|
||||
mistune = "^3.0.2"
|
||||
|
||||
|
||||
[build-system]
|
||||
|
|
13
site/blog/aoc2023-learning/content.md
Normal file
13
site/blog/aoc2023-learning/content.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: Things I Learnt During AoC 2023
|
||||
publishedDate: 2022-05-18
|
||||
updatedDate:
|
||||
- 2023-07-09
|
||||
- 2024-05-26
|
||||
- 2022-05-26
|
||||
image: null
|
||||
tags:
|
||||
- manifesto
|
||||
favourite: true
|
||||
---
|
||||

|
BIN
site/blog/aoc2023-learning/paralol.jpg
Normal file
BIN
site/blog/aoc2023-learning/paralol.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: The Modern Web Sucks
|
||||
description: And what can be done about it
|
||||
date: 2022-05-18
|
||||
publishedDate: 2022-05-18
|
||||
updatedAt:
|
||||
- 2022-05-26
|
||||
image: null
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
title: "abi" # used in the header bar and the page titles
|
||||
title: "Abi K. Pain" # used in the header bar and the page titles
|
||||
|
||||
person:
|
||||
firstName: "Abigail"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<meta charset="UTF-8"/>
|
||||
<title>{% if page.title %}{{ page.title }} :: {% endif %}{{ site.title }}</title>
|
||||
{% set __title %}{% if page.title %}{{ page.title }} :: {% endif %}{{ site.title }}{% endset %}
|
||||
<title>{{ __title }}</title>
|
||||
<meta property="og:title" content="{{ __title }}">
|
||||
{% if page.description %}<meta name="description" content="{{ page.description }}">{% endif %}
|
||||
{% if page.canonicalURL %}<link rel="canonical" href="{{ page.canonicalURL }}">{% endif %}
|
||||
{% if page.imageURL %}<meta property="og:image" content="{{ page.imageURL }}">{% endif %}
|
||||
|
@ -10,7 +12,6 @@
|
|||
|
||||
<link rel="stylesheet" href="/assets/fontawesome/css/all.min.css">
|
||||
|
||||
<link rel="stylesheet" href="/assets/css/house-palette.css">
|
||||
<link rel="stylesheet" href="/assets/css/risotto.css">
|
||||
<link rel="stylesheet" href="/assets/css/custom.css">
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<nav class="page__nav main-nav">
|
||||
<ul>
|
||||
<h1 class="page__logo"><a href="{{ site.baseURL }}" class="page__logo-inner">{{ site.title }}</a></h1>
|
||||
<h1 class="page__logo"><a href="{{ site.baseURL }}" class="page__logo-inner">{{ site.title | lower }}</a></h1>
|
||||
<!-- TODO: Add `active` class to these anchors -->
|
||||
{% for item in site.menu %}
|
||||
<li class="main-nav__item"><a class="nav-main-item" href="{{ item.url }}" {% if item.title %}title="{{ item.title }}"{% endif %}>{{ item.name }}</a></li>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
<head>
|
||||
{% include "_includes/head.html" %}
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
24
site/content/_layouts/blog_index.html
Normal file
24
site/content/_layouts/blog_index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
{% extends "_layouts/base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="alternate" type="application/atom+xml" href="/blog/feed.atom">
|
||||
<link rel="alternate" type="application/json" href="/blog/feed.json">
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h1>Blog</h1>
|
||||
|
||||
<p>This blog has both an <a href="/blog/feed.atom">Atom</a> and a <a href="/blog/feed.json">JSON</a> feed.</p>
|
||||
|
||||
<ul>
|
||||
{% for (slug, title, _, date, _) in posts %}
|
||||
<li>
|
||||
{{ date | fmtdate }} :: <a href="/blog/{{ slug }}">{{ title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
// TODO(akp): this
|
||||
{% endblock %}
|
|
@ -1,11 +1,18 @@
|
|||
{% extends "_layouts/base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="alternate" type="application/atom+xml" href="/blog/feed.atom">
|
||||
<link rel="alternate" type="application/json" href="/blog/feed.json">
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h1>{{ post.title }}</h1>
|
||||
{% if post.description %}
|
||||
<p class="secondary">{{ post.description }}</p>
|
||||
<p class="small-margin">{{ post.description }}</p>
|
||||
{% endif %}
|
||||
<p class="small-margin secondary"><i>{{ post.publishedDate | fmtdate }}{% if post.updatedDate and post.updatedDate | length >= 1 %} (updated {{ post.updatedDate.0 }}){% endif %}</i></p>
|
||||
<hr>
|
||||
{{ content | safe }}
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
|
|
|
@ -8,4 +8,9 @@ div.padding {
|
|||
|
||||
a:hover > img {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/* base16 default dark
|
||||
* https://github.com/chriskempson/base16-default-schemes
|
||||
*/
|
||||
|
||||
:root {
|
||||
--base00: #181818;
|
||||
--base01: #282828;
|
||||
--base02: #383838;
|
||||
--base03: #585858;
|
||||
--base04: #b8b8b8;
|
||||
--base05: #d8d8d8;
|
||||
--base06: #e8e8e8;
|
||||
--base07: #f8f8f8;
|
||||
--base08: #ab4642;
|
||||
--base09: #dc9656;
|
||||
--base0A: #f7ca88;
|
||||
--base0B: #a1b56c;
|
||||
--base0C: #86c1b9;
|
||||
--base0D: #7cafc2;
|
||||
--base0E: #ba8baf;
|
||||
--base0F: #a16946;
|
||||
}
|
|
@ -65,6 +65,10 @@ p {
|
|||
margin: 0 0 1.5rem 0;
|
||||
}
|
||||
|
||||
p.small-margin {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
|
||||
a:link, a:visited {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue