Fix various broken parts of posts templates

This commit is contained in:
Sami Samhuri 2019-12-05 09:12:04 -08:00
parent 0a876c0c01
commit 5fac69542c
9 changed files with 85 additions and 57 deletions

View file

@ -73,14 +73,16 @@ Execution, trying TDD for the first time:
- [x] Search for other _data.json and .ejs files and eliminate any that are found
- [ ] Link years to year indexes in the posts archive
- [x] Link years to year indexes in the posts archive
- [ ] Fix missing days on post dates in the archive and year indexes
- [x] Fix missing days on post dates in the archive and year indexes
- [ ] Find a way to add the site name to HTML titles rendered by plugins
- [x] Find a way to add the site name to HTML titles rendered by plugins
- [ ] Clean up the posts plugin
- [ ] Why don't plain data structures always work with Stencil? Maybe computed properties are a no-go but we can at least use structs instead of dictionaries for the actual rendering
- [ ] Consider using Swift for samhuri.net as well, and then making SiteGenerator a package that it uses ... then we can use Plot or pointfree.co's swift-html
- [ ] Replace remaining Ruby with Swift

View file

@ -12,14 +12,6 @@ struct PageContext {
let body: String
let page: Page
let metadata: [String: String]
var title: String {
guard !page.title.isEmpty else {
return site.title
}
return "\(site.title): \(page.title)"
}
}
extension PageContext: TemplateContext {
@ -30,7 +22,6 @@ extension PageContext: TemplateContext {
var dictionary: [String: Any] {
[
"site": site,
"title": title,
"body": body,
"page": page,
"metadata": metadata,

View file

@ -17,20 +17,28 @@ struct Post {
let tags: [String]
let bodyMarkdown: String
var isLink: Bool {
link != nil
var dictionary: [String: Any] {
var result: [String: Any] = [
"slug": slug,
"title": title,
"author": author,
"day": date.day,
"month": date.month,
"year": date.year,
"formattedDate": formattedDate,
"tags": tags
]
if let link = link {
result["isLink"] = true
result["link"] = link
}
return result
}
var path: String {
let dateComponents = Calendar.current.dateComponents([.year, .month], from: date)
let year = dateComponents.year!
let month = dateComponents.month!
return "/" + [
"posts",
String(format: "%02d", year),
String(format: "%02d", month),
"\(slug)",
].joined(separator: "/")
func dictionary(withPath path: String) -> [String: Any] {
var dict = dictionary
dict["path"] = path
return dict
}
}

View file

@ -62,8 +62,8 @@ final class PostsPlugin: Plugin {
print("renderPostsByDate(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
for post in posts.flattened() {
let monthDir = postsDir
.appendingPathComponent(String(format: "%02d", post.date.year))
.appendingPathComponent(String(format: "%02d", post.date.month))
.appendingPathComponent("\(post.date.year)")
.appendingPathComponent(Month(post.date.month).padded)
try renderPost(post, monthDir: monthDir, templateRenderer: templateRenderer)
}
}
@ -73,11 +73,10 @@ final class PostsPlugin: Plugin {
let recentPosts = posts.flattened().prefix(10)
let renderedRecentPosts: [[String: Any]] = recentPosts.map { post in
let html = markdownParser.html(from: post.bodyMarkdown)
return RenderedPost(post: post, body: html).dictionary
let path = self.path(for: post)
return RenderedPost(path: path, post: post, body: html).dictionary
}
#warning("FIXME: get the site name out of here somehow")
let recentPostsHTML = try templateRenderer.renderTemplate(name: "recent-posts", context: [
"title": "samhuri.net",
"recentPosts": renderedRecentPosts,
])
let fileURL = targetURL.appendingPathComponent(recentPostsPath)
@ -88,21 +87,23 @@ final class PostsPlugin: Plugin {
print("renderArchive(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
let allYears = posts.byYear.keys.sorted(by: >)
let allMonths = (1 ... 12).map(Month.init(_:))
let postsByMonthByYearForContext: [[String: Any]] = allYears.map { year in
let yearsWithPostsByMonthForContext: [[String: Any]] = allYears.map { year in
[
"number": "\(year)",
"months": posts[year].byMonth.keys.sorted(by: >).map { month in
[
"number": month.padded,
"posts": posts[year][month].posts,
"path": self.path(year: year),
"title": "\(year)",
"months": posts[year].byMonth.keys.sorted(by: >).map { (month: Month) -> [String: Any] in
let sortedPosts = posts[year][month].posts.sorted(by: { $0.date > $1.date })
return [
"path": self.path(year: year, month: month),
"title": month.padded,
"posts": sortedPosts.map { $0.dictionary(withPath: self.path(for: $0)) },
]
} as Any,
},
]
}
let context: [String: Any] = [
"title": "Archive",
"path": postsPath,
"years": postsByMonthByYearForContext,
"years": yearsWithPostsByMonthForContext,
"monthNames": allMonths.reduce(into: [String: String](), { dict, month in
dict[month.padded] = month.name
}),
@ -130,11 +131,10 @@ final class PostsPlugin: Plugin {
try fileManager.createDirectory(at: yearDir, withIntermediateDirectories: true, attributes: nil)
let months = Array(sortedPostsByMonth.keys.sorted().reversed())
let postsByMonthForContext: [String: [Post]] = sortedPostsByMonth.reduce(into: [:]) { dict, pair in
let postsByMonthForContext: [String: [[String: Any]]] = sortedPostsByMonth.reduce(into: [:]) { dict, pair in
let (month, posts) = pair
dict[month.padded] = posts
dict[month.padded] = posts.map { $0.dictionary(withPath: self.path(for: $0)) }
}
#warning("FIXME: get the site name in the head title but not the body title")
let context: [String: Any] = [
"title": "\(year)",
"path": postsPath,
@ -166,12 +166,12 @@ final class PostsPlugin: Plugin {
}
let renderedPosts = sortedPosts.map { post -> RenderedPost in
let path = self.path(for: post)
let bodyHTML = markdownParser.html(from: post.bodyMarkdown)
return RenderedPost(post: post, body: bodyHTML)
return RenderedPost(path: path, post: post, body: bodyHTML)
}
let monthDir = yearDir.appendingPathComponent(month.padded)
try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil)
#warning("FIXME: get the site name in the head title but not the body title")
let context: [String: Any] = [
"title": "\(month.name) \(year)",
"posts": renderedPosts.map { $0.dictionary },
@ -187,10 +187,10 @@ final class PostsPlugin: Plugin {
print("renderPost(\(post.debugDescription), monthDir: \(monthDir), templateRenderer: \(templateRenderer)")
try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil)
let filename = "\(post.slug).html"
let path = self.path(for: post)
let postURL = monthDir.appendingPathComponent(filename)
let bodyHTML = markdownParser.html(from: post.bodyMarkdown)
let renderedPost = RenderedPost(post: post, body: bodyHTML)
#warning("FIXME: get the site name in the head title but not the body title")
let renderedPost = RenderedPost(path: path, post: post, body: bodyHTML)
let postHTML = try templateRenderer.renderTemplate(name: "post", context: [
"title": "\(renderedPost.post.title)",
"post": renderedPost.dictionary,
@ -211,4 +211,20 @@ final class PostsPlugin: Plugin {
}
}
}
private func path(for post: Post) -> String {
path(year: post.date.year, month: Month(post.date.month), filename: "\(post.slug).html")
}
private func path(year: Int) -> String {
"/\(postsPath)/\(year)"
}
private func path(year: Int, month: Month) -> String {
path(year: year).appending("/\(month.padded)")
}
private func path(year: Int, month: Month, filename: String) -> String {
path(year: year, month: month).appending("/\(filename)")
}
}

View file

@ -8,6 +8,7 @@
import Foundation
struct RenderedPost {
let path: String
let post: Post
let body: String
@ -18,9 +19,8 @@ struct RenderedPost {
"date": post.date,
"day": post.date.day,
"formattedDate": post.formattedDate,
"isLink": post.isLink,
"link": post.link as Any,
"path": post.path,
"path": path,
"body": body,
]
}

View file

@ -46,7 +46,6 @@ final class ProjectsPlugin: Plugin {
let projectsDir = targetURL.appendingPathComponent(path)
try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil)
let projectsURL = projectsDir.appendingPathComponent("index.html")
#warning("FIXME: get the site name in the head title but not the body title")
let projectsHTML = try templateRenderer.renderTemplate(name: "projects", context: [
"title": "Projects",
"projects": projects,
@ -56,7 +55,6 @@ final class ProjectsPlugin: Plugin {
for project in projects {
let filename = "\(project.title).html"
let projectURL = projectsDir.appendingPathComponent(filename)
#warning("FIXME: get the site name in the head title but not the body title")
let projectHTML = try templateRenderer.renderTemplate(name: "project", context: [
"title": "\(project.title)",
"project": project,

View file

@ -45,6 +45,6 @@ do {
exit(0)
}
catch {
fputs("error: \(error.localizedDescription)", stderr)
fputs("error: \(error)", stderr)
exit(-1)
}

View file

@ -2,24 +2,31 @@
{% block body %}
<div class="container">
<h1>{{ title }}</h1>
</div>
{% for year in years %}
<div class="container">
<h1>{{ year.number }}</h1>
<h2><a href="{{ year.path }}">{{ year.title }}</a></h2>
{% for month in year.months %}
<h2>
<a href="/{{ path }}/{{ year.number }}/{{ month.number }}">{{ monthNames[month.number] }}</a>
</h2>
<h3>
<a href="{{ month.path }}">{{ monthNames[month.title] }}</a>
</h3>
<ul class="archive">
{% for post in month.posts %}
<li>
{% if post.isLink %}
<a href="{{ post.path }}">&rarr; {{ post.title }}</a>
<a href="{{ post.link }}">&rarr; {{ post.title }}</a>
{% else %}
<a href="{{ post.path }}">{{ post.title }}</a>
{% endif %}
<time>{{ post.day }} {{ monthAbbreviations[month.number] }}</time>
<time>{{ post.day }} {{ monthAbbreviations[month.title] }}</time>
{% if post.isLink %}
<a class="permalink" href="{{ post.path }}">&infin;</a>
{% endif %}
</li>
{% endfor %}
</ul>

View file

@ -6,7 +6,13 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>{{ title }}</title>
{% if title %}
<title>{{ site.title }}: {{ title }}</title>
{% elif page.title %}
<title>{{ site.title }}: {{ page.title }}</title>
{% else %}
<title>{{ site.title }}</title>
{% endif %}
<link rel="icon" type="image/png" href="/images/favicon-32x32.png">
<link rel="shortcut icon" href="/images/favicon.ico">