Zine

Frequent Use Cases

About

This page collects common use cases that people will encounter while creating a Zine site.

As time passes new use cases will emerge as common enough to be listed here, so you might want to check back from time to time when wondering how to implement something in Zine.

Table of Contents

RSS Feed

Zine doesn’t have direct knowledge of RSS feeds but by combining a handful of its features you can add support for the most syndication formats commonly used.

In this example we’ll assume that we want to generate an RSS feed for a blog.

1. Define an alternative for the post listing page

Your blog should have a page that lists all posts. We’ll generate the RSS feed as an alternative version of it.

If you think about it, this is the semantically correct thing to do, since an RSS feed is an alternative version of a page that lists your posts.

posts.smd (in the frontmatter)

---
.alternatives = [{
  .name = "rss",
  .type = "application/rss+xml",
  .layout = "rss.xml",
  .output = "index.xml",
}],
---

2. Create the XML layout

SuperHTML doesn’t support XML officially yet, but there is enough support to generate correct feeds.

This is a RSS2.0 example but you can also do the same with Atom, if you prefer.

rss.xml

<rss version="2.0">
 <channel>
  <title>My blog</title>
  <link>https://site.com/</link>
  <description>Recent content</description>
  <generator>Zine -- https://zine-ssg.io</generator>
  <language>en-us</language>
  <lastBuildDate :text="$build.generated.formatHTTP()"></lastBuildDate>
  <ctx :loop="$page.subpages()">
   <item>
    <title :text="$loop.it.title"></title>
    <description :text="$loop.it.content()"></description>
    <link :text="$site.host_url.addPath($loop.it.link())"></link>
    <pubDate :text="$loop.it.date.formatHTTP()"></pubDate>
    <guid :text="$site.host_url.addPath($loop.it.link())"></guid>
   </item>
  </ctx>
 </channel>
</rss>

NOTE

The <description> element will contain the entire contents of each page listed, which will contain HTML code.

Note how we are purposely doing something weird: we are using :text instead of :html. This would normally be a mistake on our part, but in this case it’s how we succinctly request Zine to escape our HTML, which is required to output a correct RSS feed.

3. Link to your RSS feed

In SuperMD

Same page:

[Follow my RSS feed]($link.alternative('rss'))

Different page (see the reference docs for more variations):

[Follow my RSS feed]($link.page('other/page').alternative('rss'))

In SuperHTML

<ctx alt="$page.alternative('rss')">
  <a href="$ctx.alt.link()" type="$ctx.alt.type">
    RSS feed
  </a>
</ctx>

Inline Generated HTML Content in a SuperMD Document

The SuperMD docs show that you can inline HTML content in a SuperMD document by creating a =html code block:

```=hml
<h1>Hello</h1>
```

The SuperMD reference shows that among the assets that can be embedded in a document there’s also $code, which allows you to embed a source file like so:

[]($code.asset("foo.zig"))	

You can also specify the language of this code block by adding a call to language():

[]($code.asset("foo.zig").language("zig"))	

This SuperMD directive is the equivalent of embedding the contents of foo.zig into a codeblock in the document:

```zig
std.debug.print("hello world", .{});
```

You might see where we’re going with this: if you specify =html as the language, you can inline an HTML asset into your page.

[]($code.asset("my-generated-snippet.html").language("=html"))	

P.S. This works with SVGs!

Advanced Prev/Next Page

It’s trivial to add prev/next navigation to a page, but what if you would like for that logic to cross site section boundaries?

More concretely, consider the following setup:

content
├── archive
│   ├── 2024
│   │   ├── first.smd
│   │   └── index.smd
│   ├── 2025
│   │   ├── index.smd
│   │   └── second.smd
│   └── index.smd
└── index.smd

In this example archive is a section that in turn contains two other sections: 2024 and 2025.

Let’s say that you want posts in each year’s archive to link to their next and previous post but also across years.

In the example above this would mean that:

  • 2024/first.smd would have 2025/second.smd at its next page
  • 2025/second.smd would have 2024/first.smd as its previous page

Here’s how you can express this in SuperHTML:


<ctx :if="$page.prevPage?()">
  <div>
    Prev:
    <a href="$if.link()" :html="$if.title"></a>
  </div>
</ctx>
<ctx :if="$page.hasPrev().not()">
  <ctx :if="$page.parentSection().prevPage?()">
    <ctx :if="$if.subpages().first?()">
      Prev:
      <a href="$if.link()" :html="$if.title"></a>
    </ctx>
  </ctx>
</ctx>
<ctx :if="$page.nextPage?()">
  <div>
    Next:
    <a href="$if.link()" :html="$if.title"></a>
  </div>
</ctx>
<ctx :if="$page.hasNext().not()">
  <ctx :if="$page.parentSection().nextPage?()">
    <ctx :if="$if.subpages().last?()">
      Next:
      <a href="$if.link()" :html="$if.title"></a>
    </ctx>
  </ctx>
</ctx>

This logic can be roughly summed up as: if there’s no {prev, next} page go up one level (parentSection()), go to the {prev, next} section, and then navigate down (subpages()) to the {first, last} page contained.

Author / Tag Metadata

The Ziggy frontmatter lets you specify a list of authors and tags for each page, but the entries in those arrays are simple strings.

If you want to give extra metadata to those quantities (e.g. a profile picture and a bio to each author, or a logo and a description to a tag), instead of treating them as ready-to-print values, use them as keys for:

  • page navigation
  • indexing into a custom dict

For example a website for a conference could have a talks section and a speakers one:

In talk.shtml:

<ctx author="$site.page('speakers', $page.authors.at(0))">
  <h2 :text="$ctx.author.title"></h2>
  <img src="$ctx.author.asset('picture.jpg')">
  <p :html="$ctx.author.content()"></p>
</ctx>

(This could be improved by looping over all authors instead of just selecting the first one.)

In speaker.shtml:

<h2>Talks</h2>
<ul :loop="$site.page('talks').subpagesByAuthor($page.link().basename())">
  <a href="$loop.it.link()">
    <li :text="$loop.it.title"></li>
  </a>
</ul>