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 - [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 - [ ] 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 - [ ] 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 - [ ] Replace remaining Ruby with Swift

View file

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

View file

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

View file

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

View file

@ -46,7 +46,6 @@ final class ProjectsPlugin: Plugin {
let projectsDir = targetURL.appendingPathComponent(path) let projectsDir = targetURL.appendingPathComponent(path)
try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil) try fileManager.createDirectory(at: projectsDir, withIntermediateDirectories: true, attributes: nil)
let projectsURL = projectsDir.appendingPathComponent("index.html") 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: [ let projectsHTML = try templateRenderer.renderTemplate(name: "projects", context: [
"title": "Projects", "title": "Projects",
"projects": projects, "projects": projects,
@ -56,7 +55,6 @@ final class ProjectsPlugin: Plugin {
for project in projects { for project in projects {
let filename = "\(project.title).html" let filename = "\(project.title).html"
let projectURL = projectsDir.appendingPathComponent(filename) 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: [ let projectHTML = try templateRenderer.renderTemplate(name: "project", context: [
"title": "\(project.title)", "title": "\(project.title)",
"project": project, "project": project,

View file

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

View file

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

View file

@ -6,7 +6,13 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <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="icon" type="image/png" href="/images/favicon-32x32.png">
<link rel="shortcut icon" href="/images/favicon.ico"> <link rel="shortcut icon" href="/images/favicon.ico">