Devlead.Statiq - Part 1 - Tabs

An .NET assembly extending the static site generator Statiq with new core features

Published on Friday, 9 April 2021

Earlier this year I blogged about that my "Blog migrated to Statiq", one advantage with Statiq is that it's through .NET code really customizable and lets you adapt it fully to your needs. Code that can be packaged and distributed as a NuGet package, making it straightforward to share and reuse functionality between sites.

In a three-part blog post series, I'll start going through the features of the NuGet package Devlead.Statiq created for my own Statiq based sites - but probably useful for others too, and this first part will be about the TabGroup Shortcode.

TabGroup Shortcode

Statiq shortcodes are small but powerful macros that can generate content or add metadata to your documents.

The TabGroup shortcode, is a CSS-only solution to simplify adding tabs in your Statiq input files.

Why add tabs? Well with some content, a good example of that is code samples, tabs make it easier to group content together, keep things more focused and reduce user vertical scrolling.

With the TabGroup shortcode tab content can be defined as either

  • Content - markdown defined directly in the shortcode content
  • Include - markdown fetched and processed from a external file
  • Code - fetch external file into markdown code fence

the shortcode content is defined as YAML, you can within a single tab combine all variants (content, include, and code), and it'll render in the following order

  1. content
  2. include
  3. code

First of all in your Statiq App you need to add the Devlead.Statiq NuGet package to your Statiq web application.

Enabling TabGroup shortcode is done by invoking the AddTabGroupShortCode extension on your Statiq app Bootstrapper which will enable the shortcode and add the needed CSS file to your generation output.

using System;
using Devlead.Statiq.Tabs;
using Statiq.App;
using Statiq.Web;

await Bootstrapper
    .Factory
    .CreateDefault(args)
    .AddWeb()
    .AddTabGroupShortCode()
    .RunAsync();

The CSS file will end up in output/scss/TabGroup.css and you'll need to reference it where you link in your other CSS custom files (i.e. _head.cshtml), but for your convenience, I've created a TabGroupCss shortcode you can use i.e. with this site my _head.cshtml looks like this

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<style>
.sponsorship { text-align: center; margin-top: 1em; }
.sponsorship a { text-decoration: none; }
.sponsorship a i.fa-heart { color: red; }
.sponsorship a:hover i { color: white; transition-property: color; transition-duration: 350ms; }
</style>
<?# TabGroupCss /?>

Content

Content lets you use markdown self-contained directly within TabGroup shortcode, and is specified using tab content, using pipe (|) in YAML lets you have multiline content.

The name of a tab is specified using name property, if not specified you'll get a unique id as its name.

Example usage

<?# TabGroup ?>
<?*
tabs:
  - name: Tab Two
    content: |
      ## Famous words

      Quote some latin

      > Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elementum neque, ac pharetra tortor. Vivamus ac vehicula augue. Curabitur pretium commodo nisi id pellentesque.

      ## Useful?

      I think so!

  - name: Tab One
    content: |
      ## Useful?

      I think so!

      ## Famous words

      Quote some latin

      > Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elementum neque, ac pharetra tortor. Vivamus ac vehicula augue. Curabitur pretium commodo nisi id pellentesque.

?>
<?#/ TabGroup ?>

Result

Famous words

Quote some latin

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elementum neque, ac pharetra tortor. Vivamus ac vehicula augue. Curabitur pretium commodo nisi id pellentesque.

Useful?

I think so!

Useful?

I think so!

Famous words

Quote some latin

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elementum neque, ac pharetra tortor. Vivamus ac vehicula augue. Curabitur pretium commodo nisi id pellentesque.

Include

Include content lets you fetch markdown from an external document. This is really useful as it makes the TabGroup more readable, but it also great for code reuse and i.e. if you want the same content on multiple tabs.

Path to the markdown file is specified using tab include property, either an absolute path or relative to the document,, by leading with dot slash (./) path will be relative to input root, which simplifies reasoning where document is located, regardless of where the document is included from.

If no name property specified, by convention the tab name will be the name of the external document without extension, title cased, and underscores will be replaced by a space.

Example usage

<?# TabGroup ?>
<?*
tabs:
  - include: "./../includes/posts/2021/devlead-statiq/tabgroup/src/lorem_ipsum.md"

  - name: Famous words
    include: "./../includes/posts/2021/devlead-statiq/tabgroup/src/lorem_ipsum.md"
?>
<?#/ TabGroup ?>

Result

Famous words

Quote some latin

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elementum neque, ac pharetra tortor. Vivamus ac vehicula augue. Curabitur pretium commodo nisi id pellentesque.

Useful?

I think so!

Famous words

Quote some latin

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elementum neque, ac pharetra tortor. Vivamus ac vehicula augue. Curabitur pretium commodo nisi id pellentesque.

Useful?

I think so!

Code

Code content lets you have fetch content from an external document into a markdown code fence. This is really useful as it makes the TabGroup more readable, but it also great for code reuse and i.e. keeping documentation in sync actual code used.

Path to code file is specified using tab code property, either absolute path or relative to document, by leading with dot slash (./) path will be relative to input root, which simplifies reasoning where document is located, regardless of where the document is included from.

If no name property specified the name of the document including extension (with preserved casing) will be used as tab name.

Code language used for code fence will be automatically inferred from file extension but you can override it using the codeLang property.

Example usage

<?# TabGroup ?>
<?*
tabs:
  - code: ./../settings.yml

  - name: Settings yaml file
    code: ./../settings.yml

  - code: ./_head.cshtml

  - code: ./_head.cshtml
    codeLang: html

?>
<?#/ TabGroup ?>

Result

Host: "www.devlead.se"
SiteTitle: "@devlead - Mattias Karlsson's Blog"
FeedTitle: "@devlead - Mattias Karlsson's Blog"
PostSources: "posts/**/*"
Image: "https://cdn.devlead.se/clipimg-vscode/2021/01/11/1f2af322-a19f-753d-bd3e-00118dc674f1.png?sv=2019-12-12&st=2021-01-10T15%3A16%3A48Z&se=2031-01-11T15%3A16%3A48Z&sr=b&sp=r&sig=RYHdZjTvO%2Fw0tUGsgnIiGJRhTiQQPHnEsSKtnV14Yoc%3D"
Copyright: => $"© Mattias Karlsson {DateTime.Now.Year}"
LinksUseHttps: true
EditLink: => "https://github.com/devlead/devlead.se/edit/main/src/DevLead/input/" + doc.Source.GetRelativeInputPath()
DateTimeDisplayCulture: "en-SE"
Host: "www.devlead.se"
SiteTitle: "@devlead - Mattias Karlsson's Blog"
FeedTitle: "@devlead - Mattias Karlsson's Blog"
PostSources: "posts/**/*"
Image: "https://cdn.devlead.se/clipimg-vscode/2021/01/11/1f2af322-a19f-753d-bd3e-00118dc674f1.png?sv=2019-12-12&st=2021-01-10T15%3A16%3A48Z&se=2031-01-11T15%3A16%3A48Z&sr=b&sp=r&sig=RYHdZjTvO%2Fw0tUGsgnIiGJRhTiQQPHnEsSKtnV14Yoc%3D"
Copyright: => $"© Mattias Karlsson {DateTime.Now.Year}"
LinksUseHttps: true
EditLink: => "https://github.com/devlead/devlead.se/edit/main/src/DevLead/input/" + doc.Source.GetRelativeInputPath()
DateTimeDisplayCulture: "en-SE"
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<style>
.sponsorship { text-align: center; margin-top: 1em; }
.sponsorship a { text-decoration: none; }
.sponsorship a i.fa-heart { color: red; }
.sponsorship a:hover i { color: white; transition-property: color; transition-duration: 350ms; }
</style>
<?# TabGroupCss /?>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<style>
.sponsorship { text-align: center; margin-top: 1em; }
.sponsorship a { text-decoration: none; }
.sponsorship a i.fa-heart { color: red; }
.sponsorship a:hover i { color: white; transition-property: color; transition-duration: 350ms; }
</style>
<?# TabGroupCss /?>

Conclusion

I'm really happy how flexible and versatile the TabGroup shortcode ended up being, while still keeping my markdown files nice and tidy, abstracting away the complexity.

Resources