Skip to content

Instantly share code, notes, and snippets.

@bsrz
Last active September 4, 2023 15:48
Show Gist options
  • Select an option

  • Save bsrz/54a5ea369d304334519e58527eb1a3bd to your computer and use it in GitHub Desktop.

Select an option

Save bsrz/54a5ea369d304334519e58527eb1a3bd to your computer and use it in GitHub Desktop.

Getting Started With Publish

.DS_Store
/build
/.build
/.swiftpm
/*.xcodeproj
.publish
# Ignore the output
/Output
struct ItemMetadata: WebsiteItemMetadata {
// Add any site-specific metadata that you want to use here.
}
import Foundation
import Publish
import Plot
// This type acts as the configuration for your website.
struct HelloPublish: Website {
enum SectionID: String, WebsiteSectionID {
// Add the sections that you want your website to contain here:
case posts
}
struct ItemMetadata: WebsiteItemMetadata {
// Add any site-specific metadata that you want to use here.
}
// Update these properties to configure your website:
var url = URL(string: "https://your-website-url.com")!
var name = "HelloPublish"
var description = "A description of HelloPublish"
var language: Language { .english }
var imagePath: Path? { nil }
}
// This will generate your website using the built-in Foundation theme:
try HelloPublish().publish(withTheme: .foundation)
// This will generate your website using the built-in Foundation theme:
try HelloPublish().publish(withTheme: .foundation)
try HelloPublish().publish(withTheme: .hello)
enum SectionID: String, WebsiteSectionID {
// Add the sections that you want your website to contain here:
case posts
}
HelloPublish (root)
├── Content
│   ├── index.md
│   └── posts
│   ├── first-post.md
│   └── index.md
├── Mintfile
├── Output
│   ├── feed.rss
│   ├── index.html
│   ├── posts
│   │   ├── first-post
│   │   │   └── index.html
│   │   └── index.html
│   ├── sitemap.xml
│   ├── styles.css
│   └── tags
│   ├── article
│   │   └── index.html
│   ├── first
│   │   └── index.html
│   └── index.html
├── Package.resolved
├── Package.swift
├── Resources
└── Sources
└── HelloPublish
└── main.swift
Resources
├── images
└── styles
Resources
├── images
│   └── posts
│      ├── 0001
│      │   ├── hero.png
│      │   └── thumbnail.png
│      ├── 0002
│      │   ├── hero.png
│      │   └── thumbnail.png
│      ├── Nth
│      │   ├── hero.png
│      │   └── thumbnail.png
│      ↓
└── styles
└── main.css
/**
* Publish
* Copyright (c) John Sundell 2019
* MIT license, see LICENSE file for details
*/
import Plot
public extension Theme {
/// The default "Foundation" theme that Publish ships with, a very
/// basic theme mostly implemented for demonstration purposes.
static var foundation: Self {
Theme(
htmlFactory: FoundationHTMLFactory(),
resourcePaths: ["Resources/FoundationTheme/styles.css"]
)
}
}
private struct FoundationHTMLFactory<Site: Website>: HTMLFactory {
func makeIndexHTML(for index: Index,
context: PublishingContext<Site>) throws -> HTML
{
HTML(
.lang(context.site.language),
.head(for: index, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: nil)
Wrapper {
H1(index.title)
Paragraph(context.site.description)
.class("description")
H2("Latest content")
ItemList(
items: context.allItems(
sortedBy: \.date,
order: .descending
),
site: context.site
)
}
SiteFooter()
}
)
}
func makeSectionHTML(for section: Section<Site>,
context: PublishingContext<Site>) throws -> HTML
{
HTML(
.lang(context.site.language),
.head(for: section, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: section.id)
Wrapper {
H1(section.title)
ItemList(items: section.items, site: context.site)
}
SiteFooter()
}
)
}
func makeItemHTML(for item: Item<Site>,
context: PublishingContext<Site>) throws -> HTML
{
HTML(
.lang(context.site.language),
.head(for: item, on: context.site),
.body(
.class("item-page"),
.components {
SiteHeader(context: context, selectedSelectionID: item.sectionID)
Wrapper {
Article {
Div(item.content.body).class("content")
Span("Tagged with: ")
ItemTagList(item: item, site: context.site)
}
}
SiteFooter()
}
)
)
}
func makePageHTML(for page: Page,
context: PublishingContext<Site>) throws -> HTML
{
HTML(
.lang(context.site.language),
.head(for: page, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: nil)
Wrapper(page.body)
SiteFooter()
}
)
}
func makeTagListHTML(for page: TagListPage,
context: PublishingContext<Site>) throws -> HTML?
{
HTML(
.lang(context.site.language),
.head(for: page, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: nil)
Wrapper {
H1("Browse all tags")
List(page.tags.sorted()) { tag in
ListItem {
Link(tag.string,
url: context.site.path(for: tag).absoluteString)
}
.class("tag")
}
.class("all-tags")
}
SiteFooter()
}
)
}
func makeTagDetailsHTML(for page: TagDetailsPage,
context: PublishingContext<Site>) throws -> HTML?
{
HTML(
.lang(context.site.language),
.head(for: page, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: nil)
Wrapper {
H1 {
Text("Tagged with ")
Span(page.tag.string).class("tag")
}
Link("Browse all tags",
url: context.site.tagListPath.absoluteString)
.class("browse-all")
ItemList(
items: context.items(
taggedWith: page.tag,
sortedBy: \.date,
order: .descending
),
site: context.site
)
}
SiteFooter()
}
)
}
}
private struct Wrapper: ComponentContainer {
@ComponentBuilder var content: ContentProvider
var body: Component {
Div(content: content).class("wrapper")
}
}
private struct SiteHeader<Site: Website>: Component {
var context: PublishingContext<Site>
var selectedSelectionID: Site.SectionID?
var body: Component {
Header {
Wrapper {
Link(context.site.name, url: "/")
.class("site-name")
if Site.SectionID.allCases.count > 1 {
navigation
}
}
}
}
private var navigation: Component {
Navigation {
List(Site.SectionID.allCases) { sectionID in
let section = context.sections[sectionID]
return Link(section.title,
url: section.path.absoluteString)
.class(sectionID == selectedSelectionID ? "selected" : "")
}
}
}
}
private struct ItemList<Site: Website>: Component {
var items: [Item<Site>]
var site: Site
var body: Component {
List(items) { item in
Article {
H1(Link(item.title, url: item.path.absoluteString))
ItemTagList(item: item, site: site)
Paragraph(item.description)
}
}
.class("item-list")
}
}
private struct ItemTagList<Site: Website>: Component {
var item: Item<Site>
var site: Site
var body: Component {
List(item.tags) { tag in
Link(tag.string, url: site.path(for: tag).absoluteString)
}
.class("tag-list")
}
}
private struct SiteFooter: Component {
var body: Component {
Footer {
Paragraph {
Text("Generated using ")
Link("Publish", url: "https://github.com/johnsundell/publish")
}
Paragraph {
Link("RSS feed", url: "/feed.rss")
}
}
}
}
public extension Theme {
/// The default "Foundation" theme that Publish ships with, a very
/// basic theme mostly implemented for demonstration purposes.
static var foundation: Self {
Theme(
htmlFactory: FoundationHTMLFactory(),
resourcePaths: ["Resources/FoundationTheme/styles.css"]
)
}
}
import Publish
public extension Theme {
static var hello: Self {
Theme(
htmlFactory: HelloHTMLFactory(),
resourcePaths: ["Resources/ThemeHello/styles.css"]
)
}
}
private struct HelloHTMLFactory<Site: Website>: HTMLFactory {
[...]
}
func makeIndexHTML(for index: Index, context: PublishingContext<Site>) throws -> HTML
func makeSectionHTML(for section: Section<Site>, context: PublishingContext<Site>) throws -> HTML
func makeItemHTML(for item: Item<Site>, context: PublishingContext<Site>) throws -> HTML
func makePageHTML(for page: Page, context: PublishingContext<Site>) throws -> HTML
func makeTagListHTML(for page: TagListPage, context: PublishingContext<Site>) throws -> HTML?
func makeTagDetailsHTML(for page: TagDetailsPage, context: PublishingContext<Site>) throws -> HTML?
func makeIndexHTML(for index: Index, context: PublishingContext<Site>) throws -> HTML {
HTML(
.lang(context.site.language),
.head(for: index, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: nil)
Wrapper {
H1(index.title)
Paragraph(context.site.description)
.class("description")
H2("Latest content")
ItemList(
items: context.allItems(
sortedBy: \.date,
order: .descending
),
site: context.site
)
}
SiteFooter()
}
)
}
func makeSectionHTML(for section: Section<Site>, context: PublishingContext<Site>) throws -> HTML {
HTML(
.lang(context.site.language),
.head(for: section, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: section.id)
Wrapper {
H1(section.title)
ItemList(items: section.items, site: context.site)
}
SiteFooter()
}
)
}
func makeItemHTML(for item: Item<Site>, context: PublishingContext<Site>) throws -> HTML {
HTML(
.lang(context.site.language),
.head(for: item, on: context.site),
.body(
.class("item-page"),
.components {
SiteHeader(context: context, selectedSelectionID: item.sectionID)
Wrapper {
Article {
Div(item.content.body).class("content")
Span("Tagged with: ")
ItemTagList(item: item, site: context.site)
}
}
SiteFooter()
}
)
)
}
func makePageHTML(for page: Page, context: PublishingContext<Site>) throws -> HTML {
HTML(
.lang(context.site.language),
.head(for: page, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: nil)
Wrapper(page.body)
SiteFooter()
}
)
}
func makeTagListHTML(for page: TagListPage, context: PublishingContext<Site>) throws -> HTML? {
HTML(
.lang(context.site.language),
.head(for: page, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: nil)
Wrapper {
H1("Browse all tags")
List(page.tags.sorted()) { tag in
ListItem {
Link(tag.string,
url: context.site.path(for: tag).absoluteString)
}
.class("tag")
}
.class("all-tags")
}
SiteFooter()
}
)
}
func makeTagDetailsHTML(for page: TagDetailsPage, context: PublishingContext<Site>) throws -> HTML? {
HTML(
.lang(context.site.language),
.head(for: page, on: context.site),
.body {
SiteHeader(context: context, selectedSelectionID: nil)
Wrapper {
H1 {
Text("Tagged with ")
Span(page.tag.string).class("tag")
}
Link("Browse all tags",
url: context.site.tagListPath.absoluteString)
.class("browse-all")
ItemList(
items: context.items(
taggedWith: page.tag,
sortedBy: \.date,
order: .descending
),
site: context.site
)
}
SiteFooter()
}
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment