mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-03-29 09:35:54 +00:00
Render the post archive at /posts and redirect /archive
This commit is contained in:
parent
5ed68c45f8
commit
c8dc29a511
12 changed files with 169 additions and 148 deletions
18
Readme.md
18
Readme.md
|
|
@ -15,7 +15,7 @@ This version will go back to its roots and use headers at the top of markdown fi
|
|||
|
||||
Execution, trying TDD for the first time:
|
||||
|
||||
- [ ] Replace harp with custom Swift code
|
||||
- [x] Replace harp with custom Swift code
|
||||
|
||||
- [x] Write a test harness that renders a site and then checks the output with `diff -r`
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ Execution, trying TDD for the first time:
|
|||
|
||||
- [x] Check and delete _data.json
|
||||
|
||||
- [ ] Migrate posts to markdown with headers somehow
|
||||
- [x] Migrate posts to markdown with headers somehow
|
||||
|
||||
- [x] Define the new format
|
||||
|
||||
|
|
@ -63,15 +63,19 @@ Execution, trying TDD for the first time:
|
|||
|
||||
- [x] Migrate month indexes
|
||||
|
||||
- [ ] Migrate index / recent posts
|
||||
- [x] Migrate index / recent posts
|
||||
|
||||
- [ ] Migrate archive and put it at /posts/index.html, duh!
|
||||
- [x] Migrate archive and put it at /posts/index.html, duh!
|
||||
|
||||
- [ ] 301 redirect /archive to /posts, and update the header link
|
||||
- [x] 301 redirect /archive to /posts, and update the header link
|
||||
|
||||
- [ ] Check and delete _data.json filse
|
||||
- [x] Check and delete _data.json filse
|
||||
|
||||
- [ ] Search for other _data.json files and eliminate any that are found
|
||||
- [x] Search for other _data.json and .ejs files and eliminate any that are found
|
||||
|
||||
- [ ] Find a way to add the site name to HTML titles rendered by plugins
|
||||
|
||||
- [ ] Clean up the posts plugin
|
||||
|
||||
- [ ] 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
|
||||
|
||||
|
|
|
|||
|
|
@ -50,19 +50,12 @@ final class PostsPlugin: Plugin {
|
|||
return
|
||||
}
|
||||
|
||||
let recentPostsURL = targetURL.appendingPathComponent(recentPostsPath)
|
||||
try renderRecentPosts(targetFileURL: recentPostsURL, templateRenderer: templateRenderer)
|
||||
|
||||
let postsDir = targetURL.appendingPathComponent(postsPath)
|
||||
try renderYearsAndMonths(postsDir: postsDir, templateRenderer: templateRenderer)
|
||||
try renderPostsByDate(postsDir: postsDir, templateRenderer: templateRenderer)
|
||||
}
|
||||
|
||||
func renderRecentPosts(targetFileURL: URL, templateRenderer: TemplateRenderer) throws {
|
||||
print("renderRecentPosts(targetFileURL: \(targetFileURL), templateRenderer: \(templateRenderer)")
|
||||
let recentPosts = posts.flattened().prefix(10)
|
||||
let recentPostsHTML = try templateRenderer.renderTemplate(name: "recent-posts", context: ["recentPosts": recentPosts])
|
||||
try recentPostsHTML.write(to: targetFileURL, atomically: true, encoding: .utf8)
|
||||
try renderYears(postsDir: postsDir, templateRenderer: templateRenderer)
|
||||
try renderMonths(postsDir: postsDir, templateRenderer: templateRenderer)
|
||||
try renderArchive(postsDir: postsDir, templateRenderer: templateRenderer)
|
||||
try renderRecentPosts(targetURL: targetURL, templateRenderer: templateRenderer)
|
||||
}
|
||||
|
||||
func renderPostsByDate(postsDir: URL, templateRenderer: TemplateRenderer) throws {
|
||||
|
|
@ -75,49 +68,78 @@ final class PostsPlugin: Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
func renderYearsAndMonths(postsDir: URL, templateRenderer: TemplateRenderer) throws {
|
||||
print("renderYearsAndMonths(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
|
||||
func renderRecentPosts(targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
||||
print("renderRecentPosts(targetURL: \(targetURL), templateRenderer: \(templateRenderer)")
|
||||
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
|
||||
}
|
||||
#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)
|
||||
try recentPostsHTML.write(to: fileURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
func renderArchive(postsDir: URL, templateRenderer: TemplateRenderer) throws {
|
||||
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
|
||||
[
|
||||
"number": "\(year)",
|
||||
"months": posts[year].byMonth.keys.sorted(by: >).map { month in
|
||||
[
|
||||
"number": month.padded,
|
||||
"posts": posts[year][month].posts,
|
||||
]
|
||||
} as Any,
|
||||
]
|
||||
}
|
||||
let context: [String: Any] = [
|
||||
"title": "Archive",
|
||||
"path": postsPath,
|
||||
"years": postsByMonthByYearForContext,
|
||||
"monthNames": allMonths.reduce(into: [String: String](), { dict, month in
|
||||
dict[month.padded] = month.name
|
||||
}),
|
||||
"monthAbbreviations": allMonths.reduce(into: [String: String](), { dict, month in
|
||||
dict[month.padded] = month.abbreviatedName
|
||||
}),
|
||||
]
|
||||
let archiveHTML = try templateRenderer.renderTemplate(name: "posts-archive", context: context)
|
||||
let archiveURL = postsDir.appendingPathComponent("index.html")
|
||||
try archiveHTML.write(to: archiveURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
func renderYears(postsDir: URL, templateRenderer: TemplateRenderer) throws {
|
||||
print("renderYears(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
|
||||
let allMonths = (1 ... 12).map(Month.init(_:))
|
||||
for (year, monthPosts) in posts.byYear.sorted(by: { $1.key < $0.key }) {
|
||||
let yearDir = postsDir.appendingPathComponent("\(year)")
|
||||
var sortedPostsByMonth: [Month: [RenderedPost]] = [:]
|
||||
var sortedPostsByMonth: [Month: [Post]] = [:]
|
||||
for month in allMonths {
|
||||
let sortedPosts = monthPosts[month].posts.sorted(by: { $1.date < $0.date })
|
||||
guard !sortedPosts.isEmpty else {
|
||||
continue
|
||||
if !sortedPosts.isEmpty {
|
||||
sortedPostsByMonth[month] = sortedPosts
|
||||
}
|
||||
|
||||
let renderedPosts = sortedPosts.map { post -> RenderedPost in
|
||||
let bodyHTML = markdownParser.html(from: post.bodyMarkdown)
|
||||
return RenderedPost(post: post, body: bodyHTML)
|
||||
}
|
||||
sortedPostsByMonth[month] = renderedPosts
|
||||
|
||||
let monthDir = yearDir.appendingPathComponent(month.padded)
|
||||
try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil)
|
||||
#warning("FIXME: get the site name out of here somehow")
|
||||
let context: [String: Any] = [
|
||||
"title": "samhuri.net: \(month.name) \(year)",
|
||||
"posts": renderedPosts.map { $0.dictionary },
|
||||
]
|
||||
let monthHTML = try templateRenderer.renderTemplate(name: "posts-month", context: context)
|
||||
let monthURL = monthDir.appendingPathComponent("index.html")
|
||||
try monthHTML.write(to: monthURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
try fileManager.createDirectory(at: yearDir, withIntermediateDirectories: true, attributes: nil)
|
||||
let months = Array(sortedPostsByMonth.keys.sorted().reversed())
|
||||
let postsByMonthForContext: [String: [[String: Any]]] = sortedPostsByMonth.reduce(into: [:]) { dict, pair in
|
||||
let (month, renderedPosts) = pair
|
||||
dict[month.padded] = renderedPosts.map { $0.dictionary }
|
||||
let postsByMonthForContext: [String: [Post]] = sortedPostsByMonth.reduce(into: [:]) { dict, pair in
|
||||
let (month, posts) = pair
|
||||
dict[month.padded] = posts
|
||||
}
|
||||
let monthsPadded = months.map { $0.padded }
|
||||
#warning("FIXME: get the site name out of here somehow")
|
||||
#warning("FIXME: get the site name in the head title but not the body title")
|
||||
let context: [String: Any] = [
|
||||
"title": "samhuri.net: \(year)",
|
||||
"title": "\(year)",
|
||||
"path": postsPath,
|
||||
"year": year,
|
||||
"months": monthsPadded,
|
||||
"months": months.map { $0.padded },
|
||||
"monthNames": months.reduce(into: [String: String](), { dict, month in
|
||||
dict[month.padded] = month.name
|
||||
}),
|
||||
|
|
@ -132,6 +154,35 @@ final class PostsPlugin: Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
func renderMonths(postsDir: URL, templateRenderer: TemplateRenderer) throws {
|
||||
print("renderMonths(postsDir: \(postsDir), templateRenderer: \(templateRenderer)")
|
||||
let allMonths = (1 ... 12).map(Month.init(_:))
|
||||
for (year, monthPosts) in posts.byYear.sorted(by: { $1.key < $0.key }) {
|
||||
let yearDir = postsDir.appendingPathComponent("\(year)")
|
||||
for month in allMonths {
|
||||
let sortedPosts = monthPosts[month].posts.sorted(by: { $1.date < $0.date })
|
||||
guard !sortedPosts.isEmpty else {
|
||||
continue
|
||||
}
|
||||
|
||||
let renderedPosts = sortedPosts.map { post -> RenderedPost in
|
||||
let bodyHTML = markdownParser.html(from: post.bodyMarkdown)
|
||||
return RenderedPost(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 },
|
||||
]
|
||||
let monthHTML = try templateRenderer.renderTemplate(name: "posts-month", context: context)
|
||||
let monthURL = monthDir.appendingPathComponent("index.html")
|
||||
try monthHTML.write(to: monthURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func renderPost(_ post: Post, monthDir: URL, templateRenderer: TemplateRenderer) throws {
|
||||
print("renderPost(\(post.debugDescription), monthDir: \(monthDir), templateRenderer: \(templateRenderer)")
|
||||
try fileManager.createDirectory(at: monthDir, withIntermediateDirectories: true, attributes: nil)
|
||||
|
|
@ -139,9 +190,9 @@ final class PostsPlugin: Plugin {
|
|||
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 out of here somehow")
|
||||
#warning("FIXME: get the site name in the head title but not the body title")
|
||||
let postHTML = try templateRenderer.renderTemplate(name: "post", context: [
|
||||
"title": "samhuri.net: \(renderedPost.post.title)",
|
||||
"title": "\(renderedPost.post.title)",
|
||||
"post": renderedPost.dictionary,
|
||||
])
|
||||
try postHTML.write(to: postURL, atomically: true, encoding: .utf8)
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ 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 out of here somehow")
|
||||
#warning("FIXME: get the site name in the head title but not the body title")
|
||||
let projectsHTML = try templateRenderer.renderTemplate(name: "projects", context: [
|
||||
"title": "samhuri.net: Projects",
|
||||
"title": "Projects",
|
||||
"projects": projects,
|
||||
])
|
||||
try projectsHTML.write(to: projectsURL, atomically: true, encoding: .utf8)
|
||||
|
|
@ -56,9 +56,9 @@ final class ProjectsPlugin: Plugin {
|
|||
for project in projects {
|
||||
let filename = "\(project.title).html"
|
||||
let projectURL = projectsDir.appendingPathComponent(filename)
|
||||
#warning("FIXME: get the site name out of here somehow")
|
||||
#warning("FIXME: get the site name in the head title but not the body title")
|
||||
let projectHTML = try templateRenderer.renderTemplate(name: "project", context: [
|
||||
"title": "samhuri.net: \(project.title)",
|
||||
"title": "\(project.title)",
|
||||
"project": project,
|
||||
])
|
||||
try projectHTML.write(to: projectURL, atomically: true, encoding: .utf8)
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
{
|
||||
"mach-o-symbol-and-relocation-tables": {
|
||||
"id": "mach-o-symbol-and-relocation-tables",
|
||||
"author": "Sami Samhuri",
|
||||
"title": "Mach-O Symbol and Relocation Tables",
|
||||
"date": "28th June, 2015",
|
||||
"timestamp": 1435527198,
|
||||
"link": null,
|
||||
"url": "/posts/drafts/mach-o-symbol-and-relocation-tables",
|
||||
"tags": [
|
||||
|
||||
]
|
||||
},
|
||||
"09c2b186-3ce7-445c-89bf-5d5b7f830cd7": {
|
||||
"id": "09c2b186-3ce7-445c-89bf-5d5b7f830cd7",
|
||||
"author": "Sami Samhuri",
|
||||
"title": "The Case for Native",
|
||||
"date": "27th June, 2015",
|
||||
"timestamp": 1435424525,
|
||||
"link": null,
|
||||
"url": "/posts/drafts/09c2b186-3ce7-445c-89bf-5d5b7f830cd7",
|
||||
"tags": [
|
||||
|
||||
]
|
||||
},
|
||||
"security-through-obscurity-is-still-not-a-best-practice": {
|
||||
"id": "security-through-obscurity-is-still-not-a-best-practice",
|
||||
"author": "Sami Samhuri",
|
||||
"title": "security-through-obscurity-is-still-not-a-best-practice",
|
||||
"date": "20th August, 2017",
|
||||
"timestamp": 1503246688,
|
||||
"link": null,
|
||||
"url": "/posts/2017/08/security-through-obscurity-is-still-not-a-best-practice",
|
||||
"tags": [
|
||||
"rails",
|
||||
"security"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,11 @@
|
|||
---
|
||||
Title: Mach-O Symbol and Relocation Tables
|
||||
Author: Sami Samhuri
|
||||
Date: 28th June, 2015
|
||||
Timestamp: 1435527198
|
||||
Tags:
|
||||
---
|
||||
|
||||
The latest technology I've been learning is Palm's SDK for webOS,
|
||||
Mojo. My first impression is that it's a great platform and
|
||||
Palm could do a great job of 2.0 if they cut down on some of the
|
||||
|
|
@ -32,4 +40,4 @@ support for Emacs](http://www.emacswiki.org/emacs/MojoSdk)
|
|||
which provided a great base to get
|
||||
started with. There are wrappers around (all?) of the Palm SDK
|
||||
commands but it needed a bit of work to make it just do what I
|
||||
wanted with as little input and thought as possible.
|
||||
wanted with as little input and thought as possible.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
---
|
||||
Title: TBD
|
||||
Author: Sami Samhuri
|
||||
Date: 20th August, 2017
|
||||
Timestamp: 1503246688
|
||||
Tags: rails, security
|
||||
---
|
||||
|
||||
A common way to configure a Rails server for different deployment environments is to use environment variables. This is a good practice as described by [The Twelve-Factor App][12factor] and can be applied to any server framework in any language running on a Unix OS. It keeps such secrets out of your code repository which is good, and it also makes it easy to customize your application for different environments. It's a pretty solid technique.
|
||||
|
||||
[12factor]: https://12factor.net
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
---
|
||||
Title: The Case for Native
|
||||
Author: Sami Samhuri
|
||||
Date: 27th June, 2015
|
||||
Timestamp: 1435424525
|
||||
Tags:
|
||||
---
|
||||
|
||||
For the past month I've been using [Appcelerator Titanium](http://www.appcelerator.com/products/) on a two man team. We made a simple iPhone app with a tab bar with embedded nav controllers, just 10 screens or so total. We started porting it to the iPad and Android so have some experience there. It's been a pretty frustrating exercise most days. I had a lot of little complaints but didn't take the time to step back and look at the bigger picture. I love JavaScript and in theory Titanium is awesome and a huge win. I wanted it to be a win but in reality it just hasn't been.
|
||||
|
||||
Here are 9 reasons why native is better in the short and long run.
|
||||
|
|
@ -79,8 +79,10 @@ Redirect 301 /json-diff http://tlrobinson.net/projects/javascript-fun/jsondiff
|
|||
# The great vowel shortage of 1974 is over.
|
||||
Redirect 301 /proj /projects
|
||||
|
||||
Redirect 301 /archive /posts
|
||||
Redirect 301 /archive/index.html /posts
|
||||
Redirect 301 /blog/sjs.rss /feed.xml
|
||||
Redirect 301 /blog /posts
|
||||
Redirect 301 /blog /
|
||||
|
||||
# Old naming scheme, incompatible with Harp.
|
||||
Redirect 301 /blog/2006.02.08-first-post /posts/2006/02/first-post
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
<% function pad(n) { -%>
|
||||
<% return +n < 10 ? '0' + n : String(n) -%>
|
||||
<% } -%>
|
||||
|
||||
<% var _months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ') -%>
|
||||
<% function formatDate(t) { -%>
|
||||
<% var d = new Date(t) -%>
|
||||
<% return d.getDate() + ' ' + _months[d.getMonth()] -%>
|
||||
<% } -%>
|
||||
|
||||
<% function sortPosts(dir) { -%>
|
||||
<% var slugs = Object.keys(dir._data) -%>
|
||||
<% var posts = slugs.map(function(slug) { return dir._data[slug] }) -%>
|
||||
<% posts.sort(function(a, b) { -%>
|
||||
<% var t1 = a.timestamp -%>
|
||||
<% var t2 = b.timestamp -%>
|
||||
<% return t1 < t2 ? 1 : (t1 > t2 ? -1 : 0) -%>
|
||||
<% }) -%>
|
||||
<% return posts -%>
|
||||
<% } -%>
|
||||
|
||||
<% function posts(dir, year) { -%>
|
||||
<% if (!dir) return -%>
|
||||
|
||||
<h3>
|
||||
<a href="/posts/<%= year %>"><%= year %></a>
|
||||
</h3>
|
||||
|
||||
<% for (var month = 12; month >= 1; month--) { -%>
|
||||
<% var monthDir = dir[pad(month)] -%>
|
||||
<% if (!monthDir) continue -%>
|
||||
<ul class="archive">
|
||||
<% var posts = sortPosts(monthDir) -%>
|
||||
<% for (var i in posts) { -%>
|
||||
<% var post = posts[i] -%>
|
||||
<% if (post.hidden) continue -%>
|
||||
<li>
|
||||
<% if (post.link) { -%>
|
||||
<a href="<%= post.url %>">→ <%= post.title %></a>
|
||||
<% } else { -%>
|
||||
<a href="<%= post.url %>"><%= post.title %></a>
|
||||
<% } -%>
|
||||
<time><%= formatDate(1000 * post.timestamp) %></time>
|
||||
</li>
|
||||
<% } -%>
|
||||
</ul>
|
||||
<% } -%>
|
||||
<% } -%>
|
||||
|
||||
<% var year = new Date().getFullYear(); -%>
|
||||
<% for (; year >= 2006; year--) { -%>
|
||||
<% posts(public.posts[year], year) -%>
|
||||
<% } -%>
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
<header>
|
||||
<h2><a href="{{ post.path }}">{{ post.title }}</a></h2>
|
||||
<time>{{ post.formattedDate }}</time>
|
||||
<a class="permalink" href="{{ post.path }}">∞</a>
|
||||
</header>
|
||||
{{ post.body }}
|
||||
</article>
|
||||
|
|
|
|||
31
templates/posts-archive.html
Normal file
31
templates/posts-archive.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{% extends "samhuri.net.html" %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% for year in years %}
|
||||
<div class="container">
|
||||
<h1>{{ year.number }}</h1>
|
||||
|
||||
{% for month in year.months %}
|
||||
<h2>
|
||||
<a href="/{{ path }}/{{ year.number }}/{{ month.number }}">{{ monthNames[month.number] }}</a>
|
||||
</h2>
|
||||
|
||||
<ul class="archive">
|
||||
{% for post in month.posts %}
|
||||
<li>
|
||||
{% if post.isLink %}
|
||||
<a href="{{ post.path }}">→ {{ post.title }}</a>
|
||||
{% else %}
|
||||
<a href="{{ post.path }}">{{ post.title }}</a>
|
||||
{% endif %}
|
||||
<time>{{ post.day }} {{ monthAbbreviations[month.number] }}</time>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
<nav>
|
||||
<ul>
|
||||
<li><a href="/about">About</a></li>
|
||||
<li><a href="/archive">Archive</a></li>
|
||||
<li><a href="/posts">Archive</a></li>
|
||||
<li><a href="/projects">Projects</a></li>
|
||||
<li class="twitter"><a href="https://twitter.com/_sjs"><i class="fa fa-twitter"></i></a></li>
|
||||
<li class="github"><a href="https://github.com/samsonjs"><i class="fa fa-github"></i></a></li>
|
||||
|
|
|
|||
Loading…
Reference in a new issue