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 aSite
into a list ofTarget
- 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
- read articles from disk (we discuss only
- 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