Zine

SuperMD Basics

Intro

Make sure to read Scripty Basics first.

SuperMD is an extension of Markdown that adds Scripty expression support.

There are plenty of Markdown dialects in the wild that add new syntax to it, and there are also plenty of templating languages that allow you to template your Markdown content.

The issue with the first approach is that new syntax complicates an already overcomplicated language (to parse) and it usually only solves very specific problems.

The issue with the second approach is that, while templating (like Hugo Shortcodes) does allow you to extend Markdown in a very general way, it usually translates into pulling layouting concerns into the content, which is the exact thing we were trying to minimize by writing Markdown in the first place.

Two Columns

For example let’s say that you would like to have the middle section of a page to have a two column layout, this is how you could solve it:

SuperMD takes a completely different approach by allowing the templating language to access the content in sections:

two-columns.smd

# [Left Section]($section.id('left'))
Lorem Ipsum

# [Right Section]($section.id('right'))
Dolor Something Something

layout.shtml

<div class="flex-row">
  <div html="$page.contentSection('left')"></div>
  <div html="$page.contentSection('right')"></div>
</div>

Design Goals

The main design goal of SuperMD is to upgrade Markdown to a full-fledged markup format that is not dependent on HTML.

Markdown is often times considered too simplistic for serious markup work (e.g. creating a book) in good part also because of its ties to HTML.

SuperMD doesn’t allow inlined HTML and instead gives you the ability to define a variety of “embedded assets” through a unified syntax.

It is planned for Zine to eventually gain support for generating PDFs (and potentially other file formats), at which point it will be critical for the content markup language to have the ability to define embedded assets (and styling elements) without using HTML as a crutch.

Roadmap

Compared to other parts of Zine, SuperMD is very new and many parts of it are still in their first design iteration. The general similarity to Markdown is set in stone for SuperMD, but actual CommonMark adherence is not guaranteed.

All other file formats in Zine have a parser written from scratch, except SuperMD (which uses cmark-gfm) and it might be that at some point we’ll switch to a more sane (parsing-wise) language that still has the same general feeling of writing Markdown.

Concepts vs Syntax

We are currently using link syntax for embedding Scripty expressions, but there are some limitations in how links are defined in the Markdown grammar that are problematic for our use case.

As we gain experience and confidence in the direction of the design we will address syntax concerns more directly. In other words, we’re prioritizing the design of concepts and delaying the design of syntax.

File Extension

SuperMD files have a .smd file extension.

Developer Tooling

We don’t have yet any developer tooling for SuperMD files, but it’s high on our list of priorities.

You should expect grammars for syntax highlighting and a language server for immediate feedback on sytnax errors.

Frontmatter

As mentioned in the main documentation page, every SuperMD file in Zine must start with a frontmatter that contains all the metadata relative to the page.

SuperMD uses Ziggy as its frontmatter data serialization language.

This is the full Ziggy Schema for the frontmatter:

frontmatter.ziggy-schema

root = Frontmatter

///A RFC 3339 date string, eg "2024-10-24T00:00:00".
@date = bytes,

struct Frontmatter {
    ///The title of this page.
    title: ?bytes,
    ///A short description that the section page has 
    ///access to.
    description: ?bytes,
    ///The main author of this page.
    author: ?bytes,
    date: ?@date,
    tags: ?[bytes],
    ///Alternative paths where this content will also be 
    ///made available.
    aliases: ?[bytes],
    ///When set to true this file will be ignored when 
    ///bulding the website.
    draft: ?bool,
    ///Path to a layout file inside of the configured 
    ///layouts directory. 
    layout: bytes,
    ///Alternative versions of this page, created by 
    ///rendering the content using a different layout. 
    ///Useful for creating RSS feeds, for example.
    alternatives: ?[Alternative],
    ///Ignore other markdown files in this directory and 
    ///any sub-directory. Can only be meaningfully set to 
    ///true for 'index.smd' pages.
    skip_subdirs: ?bool,
    ///User-defined properties that you can then reference 
    ///in templates. 
    custom: ?map[any],
}

struct Alternative {
    ///Path to a layout file inside of the configured 
    ///layouts directory.
    layout: bytes,
    ///Output path, relative to the current directory. 
    ///Use an absolute path to refer to the website's root 
    ///directory.
    output: bytes,
    ///Useful when generating `<link rel="alternate">` 
    ///elements.
    title: ?bytes,
    ///Useful when generating `<link rel="alternate">` 
    ///elements.
    type: ?bytes,
}

The frontmatter data fill become available in SuperHTML inside of instances of Page.

Markdown Syntax

SuperMD uses cmark-gfm so it supports all the common extensions (tables, strikethrough, autolinks, etc) that you can normally find in the wild.

Currently SuperMD adds only one new syntactical construct: a Scripty expression embedded in link syntax.

image example

[]($image.asset('cat.jpg'))

In normal Markdown this example would result in a (invalid) link element.

In SuperMD the resulting type of that construct depends on the value that the embedded Scripty expression evaluates to.

In the previous example the expression evaluates to an Image, making it equivalent to the vanilla ![](cat.jpg).

This might not seem particularly exciting, but things become more interesting once you realize that this way of doing things allows you to easily give ids and other attributes to both emdedded assets and pieces of content.

image example

[]($image.asset('cat.jpg').id('foo'))

rendered html

<img id="foo" src="path/to/cat.jpg">

Additionally, it also gives you the ability to define embedded assets that are not supported by Markdown without the help of HTML.

For example this how you can define a video embed:

[]($video.asset('video.mp4').loop(true).autoplay(true))

What we described up until now as “emdedded assets”, are called “rendering directives” in Zine. Most of them do describe embedded assets, but $section (as seen in the two columns example) for example does not.

Directives

NOTE

Directives are a very recent addition to Zine and new ones will be added over time. Make sure to check this page and the Scripty reference whenever you update Zine.

Most Directives are very straight forward to use. The only thing to keep in mind is that calling functions on them sets their internal state in a way that is order-independent (some might call this a builder pattern or a fluent interface).

For example both of these invocations will yield the same final result:

[]($link.page('foo').id('bar'))
[]($link.id('bar').page('foo'))

One last thing to know about Directives is that all allow you to set id() and attrs() (shorthand for attributes), which will be rendered in HTML as and id and a class attribute respectively.

You can find the full list of available Directives in the SuperMD Scripty Reference.

Section Directive

The section directive lets you split a page into content sections.

Doing so has three main benefits:

Usage

There are two main ways to use the section directive:

  1. as a top-level element
  2. as a wrapper around the text of a heading

In both cases the section directive will create a container element that wraps all subsequent markdown content until another Section Directive is found or the document ends.

example

[]($section)
# First
Lorem Ipsum

# Second
[]($image.asset('cat.jpg'))

[]($section.id('third'))
## Third

rendered html

<div>
  <h1>First</h1>
  <p>Lorem Ipsum</p>
  
  <h1>Second</h1>
  <img src="cat.jpg">
</div>

<div id="third">
  <h2>Third</h2>
</div>

The second method additionally makes the heading link to the section:

example

# [First]($section.id('first'))
Lorem Ipsum

rendered html

<div id="first">
  <h1><a href="#first">First</a></h1>
  <p>Lorem Ipsum</p>
</div>

This syntax might seem to suggest that you could create a nested section by attaching it to, say, a ## heading, but that’s not the case.

Heading levels are not taken into consideration when defining sections. In other words, sections don’t nest.

You can “attach” a section to a heading of any level, but that will immediately end any previous section.

This second way of defining a Section directive can be seen as syntax sugar for:

[]($section.id('first'))
# [First]($link.ref('first'))
Lorem Ipsum

Rendering content sections

The previous examples showcased the result of rendering the page in its entirety, but sections that are given an id can also be rendered in isolation.

When rendering a section in isolation you are expected to provide the container element in your template.

example

# [First]($section.id('first'))
Lorem Ipsum

superhtml

<div id="first" :html="$page.contentSection('first')"></div>

rendered html

<div id="first">
  <h1><a href="#first">First</a></h1>
  <p>Lorem Ipsum</p>
</div>

Vanilla Image and Link Syntax

Using vanilla image / link Markdown syntax in SuperMD is allowed, but the syntax still has to be resolved to a Directive.

This means that you can use vanilla syntax as a shortcut for most common use cases.

Links

Full URLs (i.e. inclusive of protocol) map to $link.url() and $link.new() :

[Zine](https://zine-ssg.io) 
└─➤ [Zine]($link.url('https://zine-ssg.io').new(true))

A leading # means $link.ref():

[Docs](#foo) 
└─➤ [Docs]($link.ref('foo'))

A leading / means $link.page():

[Docs](/docs/) 
└─➤ [Docs]($link.page('docs'))

A leading ./ means $link.sub():

[Reference](./scripty) 
└─➤ [Reference]($link.sub('scripty')) 

Absence of leading punctuation means $link.sibling():

[Reference](superhtml) 
└─➤ [Reference]($link.sibling('superhtml')) 

Images

Full URLs (i.e. inclusive of protocol) map to $image.url():

![](https://zine-ssg.io/picture.jpg) 
└─➤ []($image.url('https://zine-ssg.io/picture.jpg'))

Hotlinking

Embedding directly images from the network (sometimes referred to as hotlinking) is frowned upon, unless explicitly endorsed by the target host.

Additionally, links to remote assets can eventually become temporarily or permanently unavailable, making the choice of copying assets locally preferable in general.

A leading / means $image.siteAsset():

![](/logo.jpg) 
└─➤ []($image.siteAsset('logo.jpg'))

Absence of a leading / means $image.asset():

![](cat.jpg) 
└─➤ []($image.asset('cat.jpg'))

Additionally, any text put inside of ![] will be used as a caption, while the Markdown “title” text will be used to fill the alt attribute of the image:

![Caption](cat.jpg "alt text") 
└─➤ [Caption]($image.asset('cat.jpg').alt('alt text'))

Warning

In Markdown any text inside of ![] is used as the alt attribute, while the extra string after the image location is used as the title attribute.

We break compatibility because within ![] you can use Markdown syntax to style your captions, which is a capability that is lost in Markdown since alt only accepts simple strings.

As a consequence of this change, alt takes the place of title simply because it’s more important. You can still give titles to images (and more) by using explicit directive syntax, if you so wish.

Inline HTML Escape Hatch

SuperMD forbids inline HTML in order to make it possible to render content files to non-HTML formats.

That said, sometimes one only plans to target HTML and would like to be able to embed, say, a YouTube video without too much fuss.

For those cases, you can still embed arbitrary HTML in a content page by creating a =html code block, like so:

```=html
<script>alert("Yep, it's inlined alright")</script>
```

Zine will parse and validate the HTML code before inlining.

Syntax Highlighting

Zine comes with a good list of Tree Sitter grammars for syntax highlighting, but not all languages are supported.

When a language is not recognized, Zine will produce an error.

If you’d like to see support for a new language added to Zine, first find (or write :^)) a Tree Sitter grammar for it, and then let us know that you’d like to see it added to Zine.

In the future Zine will support the ability for users to add grammars independently of the main project.

Next Steps