⫶☰ Table of contents

Hacklab Interface

About

The Hacklab Interface is the program that generates the static website for hacklab.nl. The images and text are compressed and combined, and synced to the website. The layout is styled with tailwindcss. The content is stored in markdown files, and has special metadata (frontmatter).

Requirements

  • Maudit is a static site builder library
  • Markdown
  • Rust
  • Tailwind

Setup

Setup maudit rust project

cargo new mywebsite
cd mywebsite
cargo add maudit
cargo add maud
cargo add serde
cargo add chrono --features serde

Setup tailwindcss

Create a package.json

npm add @tailwindcss/cli
npm add tailwindcss

Setup project structure

mkdir assets
mkdir -p content/articles
mkdir static

setup main.rs

use maud::{DOCTYPE, Markup, PreEscaped, html};
use maudit::content::markdown_entry;
use maudit::route::prelude::*;
use maudit::{
    AssetsOptions, BuildOptions, BuildOutput, PrefetchOptions, PrefetchStrategy,
    content::glob_markdown, content_sources, coronate, routes,
};

// layout

pub fn hero(ctx: &mut PageContext) -> Markup {
    let back_image = ctx
        .assets
        .add_image_with_options(
            "assets/back.jpg",
            ImageOptions {
                width: None,
                height: None,
                format: Some(maudit::assets::ImageFormat::WebP),
            },
        )
        .unwrap();
    let url = back_image.url();
    let back_style = format!("background-color: black; background-image: url('{}');",
      url);

    html! {
        section.bg-white.dark:bg-gray-900 style=(back_style) {
          h1.mb-4.text4l.font-extrabold.text-gray.text-center.h-20
            md:text-5xl.lg:text-6xl.dark:text-white {
            "Hello"
          }
       }
    }
}

pub fn layout(content: Markup, hero: Option<Markup>, ctx: &mut PageContext) -> Markup {
    ctx.assets
        .include_style_with_options("assets/hli.css",
            StyleOptions { tailwind: true }
        )
        .unwrap();
    html! {
             (DOCTYPE)
             html lang="en" {
                 head {
                     meta charset="utf-8";
                     meta name="viewport" content="width=device-width, initial-scale=1";
                     title { "hacklab" }
                     meta name="description" content="Programming blog of David";
                 }
                 body.bg-blue-950.text-blue-100 {
            (hero.unwrap_or_default())
            div {
                (content)
            }
             }
     }
    }
}
// content types

#[markdown_entry]
pub struct ArticleContent {
    pub title: String,
    pub description: String,
    pub category: String,
}

//content structs

#[route("/")]
pub struct Index;

impl Route for Index {
    fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> {
        let articles = ctx.content::<ArticleContent>("articles");
        let hero = hero(ctx);
        let articles = html! {
           ul {
            @for entry in articles.entries() {
                     @let article = entry.data(ctx);
                     @let url = &Article.url(ArticleParams { article: entry.id.clone() });
             li {
                a href=(url) {
                  (article.title)
                }
                p { (article.description) }
              }
           }

           }
        };
        layout(articles, Some(hero), ctx)
    }
}

#[route("/articles/[article]")]
pub struct Article;

#[derive(Params, Clone)]
pub struct ArticleParams {
    pub article: String,
}

impl Route<ArticleParams> for Article {
    fn pages(&self, ctx: &mut DynamicRouteContext) -> Pages<ArticleParams> {
        let articles = ctx.content::<ArticleContent>("articles");

        articles.into_pages(|entry| {
            Page::from_params(ArticleParams {
                article: entry.id.clone(),
            })
        })
    }

    fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> {
        let params = ctx.params::<ArticleParams>();
        let articles = ctx.content::<ArticleContent>("articles");
        let article = articles.get_entry(&params.article);
        let content = html! {
            div.max-w-7xl.mx-auto.px-6.py-6 {
                h3.text-3xl.md:text-4xl.font-black.uppercase.mb-8 {
                    (article.data(ctx).title)
                }
                section.prose.prose-lg.max-w-none {
                    (PreEscaped(article.render(ctx)))
                }
            }
        };
        layout(content, None, ctx)
    }
}

fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> {
    coronate(
        // add the index route, routes for artciles, and the about page
        routes![Index, Article],
        // add markdown content articles from the content/articles directory, content has the ".md" extension
        content_sources![
            "articles" => glob_markdown::<ArticleContent>("content/articles/*.md")
        ],
        BuildOptions {
            // build options
            // set the path of the tailwindcss binary, that is installed with @tailwindcss/cli
            assets: AssetsOptions {
                tailwind_binary_path: "./node_modules/.bin/tailwindcss".into(),
                ..Default::default()
            },
            // disable the prefetch js scripts
            prefetch: PrefetchOptions {
                strategy: PrefetchStrategy::None,
                ..Default::default()
            },
            ..Default::default()
        },
    )
}

In assets/hli.css the tailwind css

@import "tailwindcss";
.prose ul {
  list-style-type: disc;
  margin-top: 1em;
  margin-bottom: 1em;
  padding-left: 1.5em;
}

.prose li {
  margin-top: 0.5em;
  margin-bottom: 0.5em;
}

.prose a {
  text-decoration-line: underline;
}

.prose p {
  margin-top: 1em;
  margin-bottom: 1em;
  line-height: 1.75;
}

Content

In content/articles/hello.md

---
title: Hacklab Interface
description: Hacklab Interface is a static website written in Rust and Markdown build with maudit.
category: web
---

## Hi there

Background image

wget https://placecats.com/1024/200 -O assets/back.jpg

Start the initial build

This is quite slow, cargo needs to download and compile all the packages.

maudit dev

Deploy

rm -rf dist
maudit build
rsync -vr dist/ you@server:/var/www/mysite