mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
Render an RSS feed
This commit is contained in:
parent
ed9ad222b2
commit
d69275ce29
20 changed files with 245 additions and 39 deletions
|
|
@ -105,6 +105,8 @@ Execution, trying TDD for the first time:
|
||||||
|
|
||||||
- [x] Munge HTML files to make them available without an extension (index.html hack, do it in the SiteGenerator)
|
- [x] Munge HTML files to make them available without an extension (index.html hack, do it in the SiteGenerator)
|
||||||
|
|
||||||
|
- [ ] Use perf tools on beta.samhuri.net and compare to samhuri.net to see if inlining css and minifying JS is actually worthwhile
|
||||||
|
|
||||||
- [ ] Inline CSS?
|
- [ ] Inline CSS?
|
||||||
|
|
||||||
- [ ] Minify JS? Now that we're keeping node, why not ...
|
- [ ] Minify JS? Now that we're keeping node, why not ...
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,15 @@
|
||||||
"revision": "0e9a78d6584e3812cd9c09494d5c7b483e8f533c",
|
"revision": "0e9a78d6584e3812cd9c09494d5c7b483e8f533c",
|
||||||
"version": "0.13.1"
|
"version": "0.13.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "HTMLEntities",
|
||||||
|
"repositoryURL": "https://github.com/IBM-Swift/swift-html-entities.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "744c094976355aa96ca61b9b60ef0a38e979feb7",
|
||||||
|
"version": "3.0.14"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,13 @@ let package = Package(
|
||||||
.macOS(.v10_15),
|
.macOS(.v10_15),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.0"),
|
.package(url: "https://github.com/IBM-Swift/swift-html-entities.git", from: "3.0.0"),
|
||||||
.package(url: "https://github.com/johnsundell/ink.git", from: "0.1.0"),
|
.package(url: "https://github.com/johnsundell/ink.git", from: "0.1.0"),
|
||||||
|
.package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.0"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target( name: "SiteGenerator", dependencies: [
|
.target( name: "SiteGenerator", dependencies: [
|
||||||
|
"HTMLEntities",
|
||||||
"Ink",
|
"Ink",
|
||||||
"Stencil",
|
"Stencil",
|
||||||
]),
|
]),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// PostRepo+Feeds.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension PostRepo {
|
||||||
|
var feedPostsCount: Int { 30 }
|
||||||
|
|
||||||
|
var postsForFeed: [Post] {
|
||||||
|
Array(sortedPosts.prefix(feedPostsCount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// RSSFeedPlugin.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class RSSFeedPlugin: Plugin {
|
||||||
|
let postRepo: PostRepo
|
||||||
|
let rssWriter: RSSWriter
|
||||||
|
|
||||||
|
init(
|
||||||
|
postRepo: PostRepo = PostRepo(),
|
||||||
|
rssWriter: RSSWriter = RSSWriter()
|
||||||
|
) {
|
||||||
|
self.postRepo = postRepo
|
||||||
|
self.rssWriter = rssWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Plugin methods
|
||||||
|
|
||||||
|
func setUp(site: Site, sourceURL: URL) throws {
|
||||||
|
guard postRepo.postDataExists(at: sourceURL) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try postRepo.readPosts(sourceURL: sourceURL, makePath: rssWriter.urlPathForPost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
||||||
|
guard !postRepo.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try rssWriter.writeFeed(postRepo.postsForFeed, site: site, to: targetURL, with: templateRenderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
111
SiteGenerator/Sources/SiteGenerator/Feeds/RSSWriter.swift
Normal file
111
SiteGenerator/Sources/SiteGenerator/Feeds/RSSWriter.swift
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
//
|
||||||
|
// RSSWriter.swift
|
||||||
|
// SiteGenerator
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2019-12-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import HTMLEntities
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct FeedSite {
|
||||||
|
let title: String
|
||||||
|
let description: String?
|
||||||
|
let url: String
|
||||||
|
|
||||||
|
init(title: String, description: String?, url: URL) {
|
||||||
|
self.title = title.htmlEscape()
|
||||||
|
self.description = description?.htmlEscape()
|
||||||
|
self.url = url.absoluteString.htmlEscape()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FeedPost {
|
||||||
|
let title: String
|
||||||
|
let date: String
|
||||||
|
let author: String
|
||||||
|
let isLink: Bool
|
||||||
|
let link: String
|
||||||
|
let guid: String
|
||||||
|
let body: String
|
||||||
|
|
||||||
|
init(
|
||||||
|
title: String,
|
||||||
|
date: String,
|
||||||
|
author: String,
|
||||||
|
link: URL?,
|
||||||
|
url: URL,
|
||||||
|
body: String
|
||||||
|
) {
|
||||||
|
self.title = title.htmlEscape()
|
||||||
|
self.date = date.htmlEscape()
|
||||||
|
self.author = author.htmlEscape()
|
||||||
|
self.isLink = link != nil
|
||||||
|
self.link = (link ?? url).absoluteString.htmlEscape()
|
||||||
|
self.guid = url.absoluteString.htmlEscape()
|
||||||
|
self.body = body.htmlEscape()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let rfc822Formatter: DateFormatter = {
|
||||||
|
let f = DateFormatter()
|
||||||
|
f.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
f.dateFormat = "EEE, d MMM yyyy HH:mm:ss ZZZ"
|
||||||
|
return f
|
||||||
|
}()
|
||||||
|
|
||||||
|
private extension Date {
|
||||||
|
var rfc822: String {
|
||||||
|
rfc822Formatter.string(from: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RSSWriter {
|
||||||
|
let fileManager: FileManager
|
||||||
|
let feedPath: String
|
||||||
|
let postsPath: String
|
||||||
|
|
||||||
|
var baseURL: URL!
|
||||||
|
|
||||||
|
init(fileManager: FileManager = .default, feedPath: String = "feed.xml", postsPath: String = "posts") {
|
||||||
|
self.fileManager = fileManager
|
||||||
|
self.feedPath = feedPath
|
||||||
|
self.postsPath = postsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
#warning("These urlPath methods were copied from PostsPlugin and should possibly be moved somewhere else")
|
||||||
|
|
||||||
|
func urlPath(year: Int) -> String {
|
||||||
|
"/\(postsPath)/\(year)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlPath(year: Int, month: Month) -> String {
|
||||||
|
urlPath(year: year).appending("/\(month.padded)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlPathForPost(date: Date, slug: String) -> String {
|
||||||
|
urlPath(year: date.year, month: Month(date.month)).appending("/\(slug)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFeed(_ posts: [Post], site: Site, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
||||||
|
let renderedPosts: [FeedPost] = try posts.map { post in
|
||||||
|
return FeedPost(
|
||||||
|
title: post.title,
|
||||||
|
date: post.date.rfc822,
|
||||||
|
author: "\(site.email) (\(post.author))",
|
||||||
|
link: post.link,
|
||||||
|
url: site.url.appendingPathComponent(post.path),
|
||||||
|
body: try templateRenderer.renderTemplate(name: "feed-post.html", context: [
|
||||||
|
"post": post,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let feedXML = try templateRenderer.renderTemplate(name: "feed.xml", context: [
|
||||||
|
"site": FeedSite(title: site.title, description: site.description, url: site.url),
|
||||||
|
"feedURL": site.url.appendingPathComponent(feedPath).absoluteString.htmlEscape(),
|
||||||
|
"posts": renderedPosts,
|
||||||
|
])
|
||||||
|
let feedURL = targetURL.appendingPathComponent(feedPath)
|
||||||
|
try feedXML.write(to: feedURL, atomically: true, encoding: .utf8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,13 +33,13 @@ public final class Generator {
|
||||||
self.renderers = renderers
|
self.renderers = renderers
|
||||||
|
|
||||||
for plugin in plugins {
|
for plugin in plugins {
|
||||||
try plugin.setUp(sourceURL: sourceURL)
|
try plugin.setUp(site: site, sourceURL: sourceURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func generate(targetURL: URL) throws {
|
public func generate(targetURL: URL) throws {
|
||||||
for plugin in plugins {
|
for plugin in plugins {
|
||||||
try plugin.render(targetURL: targetURL, templateRenderer: templateRenderer)
|
try plugin.render(site: site, targetURL: targetURL, templateRenderer: templateRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
let publicURL = sourceURL.appendingPathComponent("public")
|
let publicURL = sourceURL.appendingPathComponent("public")
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,10 @@ import Foundation
|
||||||
/// This is used to make the JSON simpler to write with optionals.
|
/// This is used to make the JSON simpler to write with optionals.
|
||||||
struct HumanSite: Codable {
|
struct HumanSite: Codable {
|
||||||
let author: String
|
let author: String
|
||||||
|
let email: String
|
||||||
let title: String
|
let title: String
|
||||||
let url: String
|
let description: String?
|
||||||
|
let url: URL
|
||||||
let template: String?
|
let template: String?
|
||||||
let styles: [String]?
|
let styles: [String]?
|
||||||
let scripts: [String]?
|
let scripts: [String]?
|
||||||
|
|
@ -19,11 +21,15 @@ struct HumanSite: Codable {
|
||||||
|
|
||||||
extension Site {
|
extension Site {
|
||||||
init(humanSite: HumanSite) {
|
init(humanSite: HumanSite) {
|
||||||
self.author = humanSite.author
|
self.init(
|
||||||
self.title = humanSite.title
|
author: humanSite.author,
|
||||||
self.url = humanSite.url
|
email: humanSite.email,
|
||||||
self.template = humanSite.template ?? "page"
|
title: humanSite.title,
|
||||||
self.styles = humanSite.styles ?? []
|
description: humanSite.description,
|
||||||
self.scripts = humanSite.scripts ?? []
|
url: humanSite.url,
|
||||||
|
template: humanSite.template ?? "page",
|
||||||
|
styles: humanSite.styles ?? [],
|
||||||
|
scripts: humanSite.scripts ?? []
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public protocol Plugin {
|
public protocol Plugin {
|
||||||
func setUp(sourceURL: URL) throws
|
func setUp(site: Site, sourceURL: URL) throws
|
||||||
|
|
||||||
func render(targetURL: URL, templateRenderer: TemplateRenderer) throws
|
func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ import Foundation
|
||||||
|
|
||||||
public struct Site {
|
public struct Site {
|
||||||
public let author: String
|
public let author: String
|
||||||
|
public let email: String
|
||||||
public let title: String
|
public let title: String
|
||||||
public let url: String
|
public let description: String?
|
||||||
|
public let url: URL
|
||||||
public let template: String
|
public let template: String
|
||||||
public let styles: [String]
|
public let styles: [String]
|
||||||
public let scripts: [String]
|
public let scripts: [String]
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,6 @@ final class SiteTemplateRenderer: TemplateRenderer {
|
||||||
func renderTemplate(name: String?, context: [String: Any]) throws -> String {
|
func renderTemplate(name: String?, context: [String: Any]) throws -> String {
|
||||||
let siteContext = SiteContext(site: site, template: name)
|
let siteContext = SiteContext(site: site, template: name)
|
||||||
let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new })
|
let contextDict = siteContext.dictionary.merging(context, uniquingKeysWith: { _, new in new })
|
||||||
return try stencil.renderTemplate(name: "\(siteContext.template).html", context: contextDict)
|
return try stencil.renderTemplate(name: siteContext.template, context: contextDict)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ final class PostWriter {
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writePosts(_ posts: [Post], to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writePosts(_ posts: [Post], to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
||||||
for post in posts {
|
for post in posts {
|
||||||
let postHTML = try templateRenderer.renderTemplate(name: "post", context: [
|
let postHTML = try templateRenderer.renderTemplate(name: "post.html", context: [
|
||||||
"title": post.title,
|
"title": post.title,
|
||||||
"post": post,
|
"post": post,
|
||||||
])
|
])
|
||||||
|
|
@ -55,7 +55,7 @@ extension PostWriter {
|
||||||
|
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeRecentPosts(_ recentPosts: [Post], to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writeRecentPosts(_ recentPosts: [Post], to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
||||||
let recentPostsHTML = try templateRenderer.renderTemplate(name: "recent-posts", context: [
|
let recentPostsHTML = try templateRenderer.renderTemplate(name: "recent-posts.html", context: [
|
||||||
"recentPosts": recentPosts,
|
"recentPosts": recentPosts,
|
||||||
])
|
])
|
||||||
let fileURL = targetURL.appendingPathComponent("index.html")
|
let fileURL = targetURL.appendingPathComponent("index.html")
|
||||||
|
|
@ -69,7 +69,7 @@ extension PostWriter {
|
||||||
extension PostWriter {
|
extension PostWriter {
|
||||||
func writeArchive(posts: PostsByYear, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
func writeArchive(posts: PostsByYear, to targetURL: URL, with templateRenderer: TemplateRenderer) throws {
|
||||||
let allYears = posts.byYear.keys.sorted(by: >)
|
let allYears = posts.byYear.keys.sorted(by: >)
|
||||||
let archiveHTML = try templateRenderer.renderTemplate(name: "posts-archive", context: [
|
let archiveHTML = try templateRenderer.renderTemplate(name: "posts-archive.html", context: [
|
||||||
"title": "Archive",
|
"title": "Archive",
|
||||||
"years": allYears.map { contextDictionaryForYearPosts(posts[$0]) },
|
"years": allYears.map { contextDictionaryForYearPosts(posts[$0]) },
|
||||||
])
|
])
|
||||||
|
|
@ -111,7 +111,7 @@ extension PostWriter {
|
||||||
"year": year,
|
"year": year,
|
||||||
"months": months.map { contextDictionaryForMonthPosts(posts[year][$0], year: year) },
|
"months": months.map { contextDictionaryForMonthPosts(posts[year][$0], year: year) },
|
||||||
]
|
]
|
||||||
let yearHTML = try templateRenderer.renderTemplate(name: "posts-year", context: context)
|
let yearHTML = try templateRenderer.renderTemplate(name: "posts-year.html", context: context)
|
||||||
let yearURL = yearDir.appendingPathComponent("index.html")
|
let yearURL = yearDir.appendingPathComponent("index.html")
|
||||||
try fileManager.createDirectory(at: yearDir, withIntermediateDirectories: true, attributes: nil)
|
try fileManager.createDirectory(at: yearDir, withIntermediateDirectories: true, attributes: nil)
|
||||||
try yearHTML.write(to: yearURL, atomically: true, encoding: .utf8)
|
try yearHTML.write(to: yearURL, atomically: true, encoding: .utf8)
|
||||||
|
|
@ -126,7 +126,7 @@ extension PostWriter {
|
||||||
for (year, yearPosts) in posts.byYear {
|
for (year, yearPosts) in posts.byYear {
|
||||||
for month in yearPosts.months {
|
for month in yearPosts.months {
|
||||||
let monthDir = targetURL.appendingPathComponent(urlPath(year: year, month: month))
|
let monthDir = targetURL.appendingPathComponent(urlPath(year: year, month: month))
|
||||||
let monthHTML = try templateRenderer.renderTemplate(name: "posts-month", context: [
|
let monthHTML = try templateRenderer.renderTemplate(name: "posts-month.html", context: [
|
||||||
"title": "\(month.name) \(year)",
|
"title": "\(month.name) \(year)",
|
||||||
"posts": yearPosts[month].posts.sorted(by: >),
|
"posts": yearPosts[month].posts.sorted(by: >),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ final class PostsPlugin: Plugin {
|
||||||
|
|
||||||
// MARK: - Plugin methods
|
// MARK: - Plugin methods
|
||||||
|
|
||||||
func setUp(sourceURL: URL) throws {
|
func setUp(site: Site, sourceURL: URL) throws {
|
||||||
guard postRepo.postDataExists(at: sourceURL) else {
|
guard postRepo.postDataExists(at: sourceURL) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -29,7 +29,7 @@ final class PostsPlugin: Plugin {
|
||||||
try postRepo.readPosts(sourceURL: sourceURL, makePath: postWriter.urlPathForPost)
|
try postRepo.readPosts(sourceURL: sourceURL, makePath: postWriter.urlPathForPost)
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
||||||
guard !postRepo.isEmpty else {
|
guard !postRepo.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ final class ProjectsPlugin: Plugin {
|
||||||
self.outputPath = outputPath
|
self.outputPath = outputPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func setUp(sourceURL: URL) throws {
|
func setUp(site: Site, sourceURL: URL) throws {
|
||||||
self.sourceURL = sourceURL
|
self.sourceURL = sourceURL
|
||||||
let projectsURL = sourceURL.appendingPathComponent("projects.json")
|
let projectsURL = sourceURL.appendingPathComponent("projects.json")
|
||||||
if fileManager.fileExists(atPath: projectsURL.path) {
|
if fileManager.fileExists(atPath: projectsURL.path) {
|
||||||
|
|
@ -38,7 +38,7 @@ final class ProjectsPlugin: Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
func render(site: Site, targetURL: URL, templateRenderer: TemplateRenderer) throws {
|
||||||
guard !projects.isEmpty else {
|
guard !projects.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ final class ProjectsPlugin: Plugin {
|
||||||
let projectsDir = targetURL.appendingPathComponent(outputPath)
|
let projectsDir = targetURL.appendingPathComponent(outputPath)
|
||||||
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")
|
||||||
let projectsHTML = try templateRenderer.renderTemplate(name: "projects", context: [
|
let projectsHTML = try templateRenderer.renderTemplate(name: "projects.html", context: [
|
||||||
"title": "Projects",
|
"title": "Projects",
|
||||||
"projects": projects,
|
"projects": projects,
|
||||||
])
|
])
|
||||||
|
|
@ -54,7 +54,7 @@ final class ProjectsPlugin: Plugin {
|
||||||
|
|
||||||
for project in projects {
|
for project in projects {
|
||||||
let projectURL = projectsDir.appendingPathComponent("\(project.title)/index.html")
|
let projectURL = projectsDir.appendingPathComponent("\(project.title)/index.html")
|
||||||
let projectHTML = try templateRenderer.renderTemplate(name: "project", context: [
|
let projectHTML = try templateRenderer.renderTemplate(name: "project.html", context: [
|
||||||
"title": "\(project.title)",
|
"title": "\(project.title)",
|
||||||
"project": project,
|
"project": project,
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ func main(sourcePath: String, targetPath: String) throws {
|
||||||
let targetURL = URL(fileURLWithPath: targetPath)
|
let targetURL = URL(fileURLWithPath: targetPath)
|
||||||
let generator = try Generator(
|
let generator = try Generator(
|
||||||
sourceURL: sourceURL,
|
sourceURL: sourceURL,
|
||||||
plugins: [ProjectsPlugin(), PostsPlugin()],
|
plugins: [ProjectsPlugin(), PostsPlugin(), RSSFeedPlugin()],
|
||||||
renderers: [LessRenderer(), MarkdownRenderer()]
|
renderers: [LessRenderer(), MarkdownRenderer()]
|
||||||
)
|
)
|
||||||
try generator.generate(targetURL: targetURL)
|
try generator.generate(targetURL: targetURL)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"title": "samhuri.net",
|
"title": "samhuri.net",
|
||||||
|
"description": "just some blog",
|
||||||
"author": "Sami Samhuri",
|
"author": "Sami Samhuri",
|
||||||
|
"email": "sami@samhuri.net",
|
||||||
"url": "https://samhuri.net",
|
"url": "https://samhuri.net",
|
||||||
"styles": [
|
"styles": [
|
||||||
"/css/normalize.css",
|
"/css/normalize.css",
|
||||||
|
|
|
||||||
7
templates/feed-post.html
Normal file
7
templates/feed-post.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div id="article">
|
||||||
|
<p class="time">{{ post.date }}</p>
|
||||||
|
{{ post.body }}
|
||||||
|
{% if post.isLink %}
|
||||||
|
<p><a class="permalink" href="{{ post.url }}">∞</a></p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
23
templates/feed.xml
Normal file
23
templates/feed.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<?xml-stylesheet href="https://samhuri.net/css/normalize.css" type="text/css"?>
|
||||||
|
<?xml-stylesheet href="https://samhuri.net/css/style.css" type="text/css"?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<title>{{ site.title }}</title>
|
||||||
|
<description>{{ site.description }}</description>
|
||||||
|
<link>{{ site.url }}</link>
|
||||||
|
<pubDate>{{ posts[0].date }}</pubDate>
|
||||||
|
<atom:link href="{{ feedURL }}" rel="self" type="application/rss+xml" />
|
||||||
|
|
||||||
|
{% for post in posts %}
|
||||||
|
<item>
|
||||||
|
<title>{{ post.title }}</title>
|
||||||
|
<description>{{ post.body }}</description>
|
||||||
|
<pubDate>{{ post.date }}</pubDate>
|
||||||
|
<author>{{ post.author }}</author>
|
||||||
|
<link>{{ post.link }}</link>
|
||||||
|
<guid>{{ post.guid }}</guid>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<div id="article">
|
|
||||||
{{#post}}
|
|
||||||
<p class="time">{{date}}</p>
|
|
||||||
{{{body}}}
|
|
||||||
<p><a class="permalink" href="{{url}}">∞</a></p>
|
|
||||||
{{/post}}
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<div id="article">
|
|
||||||
{{#post}}
|
|
||||||
<p class="time">{{date}}</p>
|
|
||||||
{{{body}}}
|
|
||||||
{{/post}}
|
|
||||||
</div>
|
|
||||||
Loading…
Reference in a new issue