feat: Initial commit.
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 2m50s
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 2m50s
This commit is contained in:
27
Sources/Site/Metadata.swift
Normal file
27
Sources/Site/Metadata.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
import Saga
|
||||
|
||||
/// Represents constants about the site.
|
||||
enum SiteMetadata {
|
||||
#if DEBUG
|
||||
static let url = URL(string: "http://localhost:8080")!
|
||||
#else
|
||||
static let url = URL(string: "https://mhoush.com")!
|
||||
#endif
|
||||
static let name = "mhoush"
|
||||
static let author = "Michael Housh"
|
||||
/// Summary used for metadata / twitter card for home page,
|
||||
/// also displayed at bottom of articles.
|
||||
static let summary = """
|
||||
Test saga tables.
|
||||
"""
|
||||
/// The default twitter image when linking to home page.
|
||||
static let twitterImage = "/static/images/home-twitter-image.png"
|
||||
}
|
||||
|
||||
/// Represents valid metadata for the files that are not an `article`.
|
||||
struct PageMetadata: Metadata {
|
||||
|
||||
/// The section of the website for the file.
|
||||
let section: String?
|
||||
}
|
||||
9
Sources/Site/Section.swift
Normal file
9
Sources/Site/Section.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
/// Represents different sections of the website.
|
||||
///
|
||||
/// This is used to render base layouts appropriately for the given section.
|
||||
enum Section: String {
|
||||
/// The home page of the site.
|
||||
case home
|
||||
/// The articles / blog posts of the site.
|
||||
case table
|
||||
}
|
||||
26
Sources/Site/run.swift
Normal file
26
Sources/Site/run.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
import Foundation
|
||||
import HTML
|
||||
import PathKit
|
||||
@preconcurrency import Saga
|
||||
import SagaParsleyMarkdownReader
|
||||
import SagaSwimRenderer
|
||||
|
||||
@main
|
||||
struct Run {
|
||||
static func main() async throws {
|
||||
try await Saga(input: "content", output: "deploy")
|
||||
// All the remaining markdown files will be parsed to html,
|
||||
// using the default EmptyMetadata as the Item's metadata type.
|
||||
.register(
|
||||
metadata: PageMetadata.self,
|
||||
readers: [.parsleyMarkdownReader],
|
||||
itemWriteMode: .keepAsFile, // need to keep 404.md as 404.html, not 404/index.html
|
||||
writers: [.itemWriter(swim(renderPage))]
|
||||
)
|
||||
// Run the steps we registered above
|
||||
.run()
|
||||
// All the remaining files that were not parsed to markdown, so for example images, raw html files and css,
|
||||
// are copied as-is to the output folder.
|
||||
.staticFiles()
|
||||
}
|
||||
}
|
||||
53
Sources/Site/templates/BaseLayout.swift
Normal file
53
Sources/Site/templates/BaseLayout.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
import Foundation
|
||||
import HTML
|
||||
|
||||
/// The base page layout used to render the different sections of the website.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - conocicalURL: The url for the page.
|
||||
/// - section: The section of the page.
|
||||
/// - title: The page title.
|
||||
/// - rssLink: A prefix for generating an rss feed for the page (generally only used for articles).
|
||||
/// - extraHeader: Any extra items to be placed in the `head` of the html.
|
||||
func baseLayout(
|
||||
canocicalURL: String,
|
||||
section: Section,
|
||||
title pageTitle: String,
|
||||
rssLink: String = "",
|
||||
extraHeader: NodeConvertible = Node.fragment([]),
|
||||
@NodeBuilder children: () -> NodeConvertible
|
||||
) -> Node {
|
||||
return [
|
||||
.documentType("html"),
|
||||
html(lang: "en-US") {
|
||||
generateHeader(pageTitle, extraHeader)
|
||||
body(class: "bg-page text-white pb-5 font-avenir \(section.rawValue)") {
|
||||
div(class: "content") {
|
||||
children()
|
||||
}
|
||||
footer()
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
private func footer() -> Node {
|
||||
div(class: "site-footer text-gray gray-links border-t border-light text-center pt-6 mt-8 text-sm")
|
||||
{
|
||||
p {
|
||||
"Copyright © Michael Housh 2023-\(Date().description.prefix(4))."
|
||||
}
|
||||
p {
|
||||
"Built in Swift using"
|
||||
a(href: "https://github.com/loopwerk/Saga", rel: "nofollow", target: "_blank") { "Saga" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func generateHeader(_ pageTitle: String, _ extraHeader: NodeConvertible) -> Node {
|
||||
head {
|
||||
meta(charset: "utf-8")
|
||||
title { SiteMetadata.name + ": \(pageTitle)" }
|
||||
link(href: "/static/style.css", rel: "stylesheet")
|
||||
}
|
||||
}
|
||||
36
Sources/Site/templates/RenderPage.swift
Normal file
36
Sources/Site/templates/RenderPage.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
import HTML
|
||||
import Saga
|
||||
|
||||
func renderPage(context: ItemRenderingContext<PageMetadata>) -> Node {
|
||||
let section = Section(rawValue: context.item.metadata.section ?? "")
|
||||
assert(section != nil)
|
||||
|
||||
return baseLayout(
|
||||
canocicalURL: context.item.url,
|
||||
section: section!,
|
||||
title: context.item.title
|
||||
) {
|
||||
switch section {
|
||||
case .home:
|
||||
renderHome(body: context.item.body)
|
||||
default:
|
||||
renderNonHome(body: context.item.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderHome(body: String) -> Node {
|
||||
div {
|
||||
Node.raw(body)
|
||||
}
|
||||
}
|
||||
|
||||
func renderNonHome(body: String) -> Node {
|
||||
div {
|
||||
article {
|
||||
div {
|
||||
Node.raw(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user