May 12th, 2018, 18:01 UTC

allenap.me Elm

Rewriting allenap.me in Elm

I use Elm in my day job and I like it a lot, so I rewrote my website using it. Previously I was using Hugo, a static site generator. I was not deeply involved with Hugo – I’d used an off-the-shelf theme, I had only ever wrote blog posts, and used only a couple of custom Markdown tags – so transitioning away was not difficult.

I started with a hacky Python script that rewrote the Hugo-format blog posts from:

+++
date = "2017-06-20T11:04:00+02:00"
tags = ["Rust"]
title = "Petname library in Rust"
+++

2½ years ago …

into Elm modules:

module Allenap.Posts.RustPetname exposing (post)

import Allenap.Post exposing (…)

post : Post
post =
    { slug = "rust-petname"
    , title = "Petname library in Rust"
    , subtitle = ""
    , date = parseDateTime "2017-06-20T11:04:00+02:00"
    , tags = [ "Rust" ]
    , draft = False
    , content = Markdown content defaultMarkdownOptions
    }

content =
    """2½ years ago …"""

The script also wrote out an Allenap.Posts.All module which imported all of the post modules and listed them.

This script turned out to be a good move. I iterated the data model in the Elm application a couple of times, so I could modify the script and re-run it rather than update every post’s module. I don’t have a large number of posts but it still saved a good deal of time.

Next I built an SPA using Browser.application, re-learned Flexbox, and put together something that I wasn’t ashamed to look at.

I added search using NoRedInk/elm-simple-fuzzy but later switched to rluiten/elm-text-search. Ultimately neither will scale if I write a lot more posts – I will have to add something on the back-end – but it was fun to do.

The biggest problem was getting all the navigation and URL handling working correctly. To give it a natural feel you have to do just the right thing in the navigation handlers. After some iteration it made sense: I kept shaking, if you know what I mean, trying to remove duplication and weird edges, then repeating. In time I gained a better understanding and I was able to remove the duplications and other uglinesses.

A few warts remain:

  • Syntax highlighting with highlight.js needs some extra prompting. The problem: highlight.js doesn’t know it’s in an SPA, so it doesn’t highlight code blocks when switching page, because the page itself is not being loaded in the usual sense.

    I created a port to which I send a message after each page change, asking highlight.js to highlight code sections in the page again.

  • When navigating to a new page, one expects the viewport to be at the top of the page. This being an SPA, that wasn’t guaranteed. The naïve solution is to scroll to the top with Browser.Dom.setViewport after every navigation, but then this affects the viewport when going back and forth through the browser history.

    To fix this I used a postNavigate : Cmd msg field in my model which would be popped from the model and emitted only after navigation was complete. I would populate this only when a new page was requested by the user, i.e. when clicking a link, and not when using the back and forward buttons.

  • I wanted to be clever with searching. As you type in a search term, the URL would change: with an empty search box the path in the browser’s address bar is /, but as soon as you start typing it changes to /?search=what-you-typed, letter by letter.

    This is what I implemented first, but it lost a lot of keystrokes: type “python” quickly and you might end up with “phn” in the search box. I thought the problem was because search was slow, but after some experimentation the culprit turned out to be Browser.Navigation.replaceUrl.

    With some trial and error I got it to only call replaceUrl once input had ceased for at least 250ms. This works pretty nicely, but I kind of hope I find a better way in time.

    (I would love to add this behaviour to the search on packages.elm-lang.org.)

  • Markdown footers, like the example Markdown below, don’t render:

    Here's how footers[^1] work.
    
    [^1]: Right now, they don't.
    

    Maybe these were a Hugo-specific feature?

    I’m using elm-explorations/markdown but I’ll look into pablohirafuji/elm-markdown as the start of a potential solution.

  • RSS/Atom feeds don’t work. I care about this, but I haven’t figured out how I want to address it.

My hope is that I’ll play around with this code more than I ever did with sites of my own before. Elm is a good language for this: it allows a semi-neglectful owner like myself to feel confident that, when he dips in to play around, he’s not going to break everything and cause a mess that’ll keep him up late on a Sunday evening.