Silverpoint
A journal written in Swift by Hugo A.G

So you want to theme a swift site?

I only have positive things to say about John Sundell after working with his suite of swift web framework tools. Naturally the first thing I set out to do after setting up the publish template was to personalize it with a custom theme.

This post details the steps I took in order to accomplish that. For more details on the original implementation documentation, feel free to this out.

Setting up your theme extension

Normally you would rely on a variation of customized style sheets with some scripting languages here or there (especially with frameworks such as React or Rails) to get the job done with traditional static / dynamic web frameworks.

In Publish's case, we're able to still rely on css while also relying on statically typed conventions thanks to the underlying pure swift HTML generation provided by Plot. The way we do that is by first creating a Theme extension with a single statically defined member to function as the theme's name. We will also be creating a struct that follows the HTMLFactory protocol to leverage Plot's HTML creation engine.

I will be specifying this theme to interface directly with my site as well.

NOTE: As of this post, Silverpoint was constructed as a personal github pages site and thus is named to follow that naming convention.

import Publish
import Plot

extension Theme where Site == Hggzgithubio {
    static var hggz: Self {
        Theme(
            htmlFactory: HggzHTMLFactory()
        )
    }
}

You'll notice at this point that we have yet to define our HTMLFactory struct that follows the conventions set in place for laying out our sites various html pages. We'll cover that next.

Creating your theme's HTML

At its core, all a web browser needs to display content is an html file. You can format your website in any order or style by supplying it the html files of its pages. The way Publish organizes its content is through the use of 3 logical representations: Sections, items and pages.

As pointed out on the Publish framework's readme, each Section, Item, and Page can define its own set of Content — which can range from text (like titles and descriptions), to HTML, audio, video and various kinds of metadata.

We'll start off by declaring our struct that conforms to the HTMLFactory protocol.

private struct HggzHTMLFactory: HTMLFactory {
    typealias Site = HggzGithubio
    
    func makePageHTML(for page: Page, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML { }

    func makeIndexHTML(for index: Index, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML { }

    func makeTagListHTML(for page: TagListPage, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML? { }

    func makeTagDetailsHTML(for page: TagDetailsPage, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML? { }

    func makeItemHTML(for item: Item<HggzHTMLFactory.Site>, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML { }

    func makeSectionHTML(for section: Section<HggzHTMLFactory.Site>, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML { }
}

The following snippet shows the available functions available from the HTMLFactory protocol as well as the overriding of the Site associtedtype so the generic constraints in the struct know that we're referring to our specific site. This allows us to access some of the site specific properties our site has in the factory class, like our specific sectionId's, our metadata, etc.

You can use these functions to describe how each section's HTML will look, as well as the html in all the various other parts of our site such as the specific items (our posts like this one).

When populating each of the functions, you need to provide it some type safe swift HTML. You write it using Plot's html structures in a format similar to how you would lay out regular html tags. Heres an example of how you can lay out the html for the index of the website.

func makeIndexHTML(for index: Index,
                   context: PublishingContext<Site>) throws -> HTML {
    HTML(
        .lang(context.site.language),
        .head(for: index, on: context.site),
        .body(
            .header(for: context, selectedSection: nil),
            .wrapper(
                .h1(.text(index.title)),
                .itemList(
                    for: context.allItems(
                        sortedBy: \.date,
                        order: .descending
                    ),
                    on: context.site
                )
            ),
            .footer(for: context.site)
        )
    )
}

I decided to rely heavily on the initial Foundation theme provided by JohnSundell here. Hopefully once you check it out you'll see how extensible it is to modify it for your needs.

It was in the itemList helper function that I leveraged my prettyPrint Date extension function to display dates on each post.

static func itemList<T: Website>(for items: [Item<T>], on site: T) -> Node {
    return .ul(
        .class("item-list"),
        .forEach(items) { item in
            .li(.article(
                .h1(.a(
                    .href(item.path),
                    .text(item.title)
                    )),
                .p(.class("date-text"), .text(item.date.prettyPrintDate())),
                .br(),
                .tagList(for: item, on: site),
                .p(.text(item.description))
                ))
        }
    )
}

Adding your CSS

You'll notice in the snippet above that the HTML is leveraging 2 'classes'. One for the list ('item-list') and one for the date ('date-text'). Thats no mistake. Those elements are leveraging a css style sheet for those classes.

In order to have Publish utilize your custom css, you must provide your theme a resource path pointing to it. You do this in the static member Theme declaration above.

extension Theme where Site == Hggzgithubio {
    static var hggz: Self {
        Theme(
            htmlFactory: HggzHTMLFactory(),
            resourcePaths: ["Resources/HggzTheme/styles.css"]
        )
    }
}

I created a Resources directory in the root directory with the Package.swift file and followed the convention above with a subdirectory named after the theme and the actual css file. I also leveraged the css file utilized by the Foundation theme as a starting point and modified it myself.

Testing your changes

With all this said and done, all that was left was updating the main.swift file to use your new theme. You should now be able to see your changes on the browser.

The Publish framework has a provided CLI application that handles most of the development needs for any website work. I developed the site on XCode but should have been perfectly capable handling the development on any other platform.

My personal workflow consisted of me running the local development server by running publish run in the project directory and running the site binary on XCode everytime I wanted to visualize changes. I was able to view my site on http://localhost:8000.

NOTE: As of this post, I noticed that some css changes weren't immediately reflected after refreshing the browser page. I got around this by doing a hard refresh in the browser. I used Safari and Chrome to test my changes.

Tagged with: