Layouts and extensions

By @lucasdicioccio, 1194 words, 5 code snippets, 8 links, 0images.

There are two notions of layouts in KitchenSink: article layouts and website layouts. This article first discusses how they differ, then we deep-dive in Website Layouts.

Article vs. Website layouts

Article Layouts are per-article configurations which allow to tune what a rendered-article looks like. This configuration is set in the base:build-info.json section. For instance, while writing this text, this page is an Upcoming Article, when I’ll find that the content is ready, I’ll change this configuration to a Published Article . An example configuration is as follows:.

=base:build-info.json
{"layout":"article"
,"publicationStatus":"Upcoming"
}

The layout directive indicates how KitchenSink should intepret (in a broad sense) the rest of the .cmark article. The layout not only influences the HTML output of the article content: the layout also can influence the presence of the navigation bar at the top, or just about anything. If the directive were application instead, we would have no default CSS and no navigation bar as we expect the article content to take-over the whole page. Currently, these layouts are mostly documented in the documentation about the build-info section.

In addition, KitchenSink utilises the publicationStatus to further tweak some behaviours here and there. For instance, an Upcoming article will have a warning banner. Upcoming articles will show up grayed-out in article listings. Upcoming articles will not appear in the Atom feed. We’ve yet to document all such behaviors. However, if you wonder where KitchenSink takes all these rules, we can give you the answer right away: from the Website Layout 💡!

Website Layouts carry most of the “business rules” in KitchenSink. Creating a Website Layout is a significant modification of KitchenSink. Indeed, Website Layouts control how the collection of files in the KitchenSink directory are interpreted into a website in a broad sense. The Website Layout dictates the directory structure, the HTML contents, CSS files, videos, what have you.

Writing your own Layout allows you to:

  • support more or fewer Section types
  • redefine the set of Targets and their contents

Layouts in KitchenSink are implemented in the Haskell programming language and require some firm understanding of Haskell if you want to modify a layout, let-alone build a layout from scratch.

Article layouts

Website layouts define the behaviour of Article layouts. Kitchen-Sink currently supports a single website-layout (the one for this website). For now, I’ll refer you to the build-info section documentation.

Writing website layouts

The Website Layout is so important that the KichenSink code merely speak about Layout. This section describes KitchenSink’s Layout type in depth.

Previous paragraphs introduced Website Layouts as the way to customize the business rules to turn .cmark section-files into .html and other sort of files. Hence, so far we’ve answered what is the purpose of Website Layouts. We now discuss how Website Layouts operate. As often in Haskell, the best way to describe how something works is to show and scrutinize type signatures. Hence, let’s dive-in the Layout type and see for ourselves. As of today, the Layout type is defined as follows:

data Layout ext meta summary
  = Layout
  { siteTargets :: OutputPrefix -> meta -> Site ext -> [Target ext summary]
  , extraSectiontypes :: [ExtraSectionType ext]
  }

It helps to squint a bit and ignore type-level parameters. Simplifying the above, we could write Layout as:

data Layout
  = Layout
  { siteTargets :: Site -> [Target]
  , extraSectiontypes :: [ExtraSectionType]
  }

In short, a Layout has two main purposes:

  • provide a siteTargets that turns a Site into a list of Target
  • provide a extraSectiontypes which is a list of Sections KitchenSink should learn how to parse

The type parameters ext, summary, meta and so on and so forth are required to let the Haskell compiler ensure that everything is consistent (e.g., you can only build Targets in siteTargets for an extension if the extension is declared in extraSectiontypes).

understanding siteTargets

A Layout gets to decide how to translate the in-memory represention of a whole Site into files, including their content-generation logic.

Thus what is important is to get some example of siteTargets function. And understand, at least at a shallow-level, what is a Target.

data Target ext a = Target
  { destination :: DestinationLocation
  , productionRule :: ProductionRule ext
  , summary :: a
  } deriving (Functor)

The destination is roughly the HTTP path of where the content is placed. The productionRule is roughly the IO-inducing code to generate the content (e.g., rendering some HTML, copying some file, or executing a command). Finally, the summary serves the purpose of having previews (e.g., in the search box).

In short, a Target contains enough information to locate, describe, and build some document piece of your website.

Let’s now open KitchenSink’s default siteTargets function at a first-level of details:

siteTargets :: OutputPrefix -> MetaData -> Site -> [Target]
siteTargets prefix extra site = allTargets
  where
    allTargets = mconcat
      [ embeddedGeneratorTargets
      , embeddedDataTargets
      , fmap fst articleTargets
      , imageTargets prefix site
      , dotimageTargets prefix site
      , videoTargets prefix site
      , rawTargets prefix site
      , documentTargets prefix site
      , cssTargets prefix site
      , jsTargets prefix site
      , htmlTargets prefix site
      , topicIndexesTargets (lookupSpecialArticle SpecialArticles.Topics site)
      , topicAtomTargets (lookupSpecialArticle SpecialArticles.Topics site)
      , glossaryTargets (lookupSpecialArticleSource SpecialArticles.Glossary site)
      , jsonDataTargets
      , seoTargets
      ]

Unsurprisingly, the default siteTargets parrots what the documentation pages about sections and other types of files decribe. Each family of document, each specific section in article files, each magic-file (like glossaries) gets a specific target. Each of these functions then have different techniques (e.g., HTML targets will render some HTML using an HTML-layout library, JSON targets will use Aeson-encoding of some structure etc.)

As you can guess, writing a whole new siteTargets is a lot of work. That’s why we recommend to start contacting me before jumping into such an endeavor. Longer-term I’d like to have support for templated-targets, much like Dhall, but with a mini language better-suited for markup (like Mustache or ERB for instance).

some words on extraSectiontypes

The way KitchenSink divides work operates in two phases:

  • load the Site object
    • read articles from disk (we discuss only .cmark, but other files like .png are listed too)
    • parsing content of .cmark as section format
    • evaluating .dhall sections
  • assemble targets
    • compute all the siteTargets (cf. above)
    • evaluate all the targets

If you were to extend the .cmark file with some form of new section type (e.g., you want to support some “license” section), you would have to modify KitchenSink in both phases:

  • during the load phase: you need a name ext:my-license-extension so that the loader recognizes =ext:my-license-extension like =base:main-content.cmark
  • for the assemble phase: you later need Assembler functions (that are capable of reading article sections) to decide how your license is interpreted and rendered in your Website layout.

Following the type-machinery should be enough. One remark though: there is one single “extension” parameter, so if you want to support multiple ones, you should build a sum-type of the extensions you support.

Extending KitchenSink in other forms

You may want to modify KitchenSink in ways we have not discussed yet. For intance, you may want to support new filetypes (e.g., docx documents) in an existing family of filetypes, or new families of file types altogether (e.g., source code of some form).

Such changes are feasible but not that easily. Your best chance likely is to contact me or by opening an issue on the GitHub page.

Summary

You want to modify how a given article is rendered using an existing Article layout:

  • a) modify =base:build-info.json section

You want to modify the structure of the generated HTML (or create an Article Layout):

  • a) the siteTargets function

You want to generate addition .json magic file (or similar):

  • a) the siteTargets function

You want to modify the structure of the rendered HTML:

  • a) the siteTargets function

You want to support a new section:

  • a) modify the Layout to be able to parse the new data type
  • b) modify the evalTarget function to apply the needed changes (most likely, you want to generate some extra information)

You want to support a new filetype:

  • a) contact me
  • b) modify the Site loader
  • c) modify the Layout function with whatever you need to turn the filetype into a set of targets

Other changes:

  • a) contact me