Hugo Shortcode for Mermaid
Intro
This is a short intermission post addressing two things I wanted to test for this website.
-
In my last post, I had trouble creating a shortcode for mermaid.js for diagrams
-
I got started with emacs recently and am wondering how well
hugo
works with org instead of markdown.
Quick check if this post works with org
I saw that hugo
claimed to work with org
files while I was reading the documentation for the shortcodes. As such, I'm writing this post in org
to test it out.
Testing a default shortcode with some random inset python by writing:
{{<highlight python>}}
print("hello world")
{{</highlight>}}
print("hello world")
It turns out, this works perfectly fine. I had more issues trying to learn org than with the integration with hugo.
Creating a shortcode for mermaid.js diagrams
Okay, on to the main content though. I want to create a shortcode to make mermaid diagrams.
According to the mermaid website, what we need is essentially:
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true});</script>
<div class="mermaid">
graph LR
A --- B
B-->C[fa:fa-ban forbidden]
B-->D(fa:fa-spinner);
</div>
I made the graphic on my previous post my literally embedding this into the markdown, except with the theme
set to "dark"
to make it more visible on this background.
I want to move this into a shortcode so that I could use it more easily in my blog, but also if I continue using org instead of markdown, it'd be the only way to have diagrams.
I'm planning to create a shortcode looking like:
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true});</script>
<div class="mermaid">
{{ .Inner }}
</div>
And placing this into layouts/shortcodes/mermaid.html
Ideally we'd only load this minified js on a page where we actually use it, and maybe even not load it multiple times per page, but I want to start with this just to test.
I'm testing by putting this following code in my document:
{{<mermaid>}}
graph LR
A --- B
B-->C[fa:fa-ban forbidden]
B-->D(fa:fa-spinner);
{{</mermaid>}}
I mean, it's hard to actually show it because no matter what the mermaid diagram displayed above would probably be created by the final version of my shortcode, but suffice to say this worked for me.
Stretch Goal 1: Only load mermaid if my page uses it
Now for my stretch goals, I'd like to initialize mermaid only if I'm using it in my post.
Looking through the hugo
documentation, it looks like what we need here is to actually edit my theme or add logic to my shortcode for this and include that on the pages that I want to use mermaid on. While, I would be happy to fork this theme, I don't really know much about theming and would like the updates to it. As such, I will probably take the latter approach and edit the shortcode for it. decide to fork it.
Therefore I'm editing layouts/shortcodes/mermaid.html
file with the following contents:
{{if .Params.mermaid }}
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true});</script>
{{end}}
<div class="mermaid">
{{ .Inner }}
</div>
This leads to
"shortcodes/mermaid.html" at <.Params.mermaid>: can't evaluate field mermaid in type interface {}
It looks like the params object was not declared with mermaid as a field even though I set mermaid: true
in my front matter. I assume this is because my previous posts did not have this set. I could set each one of them to mermaid: false
but that would be a lot of work, so I needed a different solution.
My next instinct is to try to use isset
, changing my content to
{{if isset .Params "mermaid" }}
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true});</script>
{{end}}
<div class="mermaid">
{{ .Inner }}
</div>
but this time, I get
"shortcodes/mermaid.html" at <isset .Params "mermaid">: error calling isset: isset unable to use key of type string as index
This is basically the example given in their documentation, so this is pretty difficult to understand. I even have worked with Golang, and these error messages look familiar, but without the context on what the surrounding Golang is trying to do, I have no idea how to fix it.
https://gohugo.io/templates/introduction/#methods-and-fields-are-accessed-via-dot-notation
I wonder if this is caused by me trying to treat a struct
as a map
. That might explain what is happening here, but I'm not sure how I'm supposed to differentiate between the two in the hugo
template. This still doesn't really make sense because the example in their documentation does use the same .Params
object. In fact trying to put the params object directly in to my page I see that even for this page, it's []
, which looks to me like an empty map. Considering that I do have keys in my front matter, this doesn't make much sense to me.
I wonder if the behavior for shortcodes has changed and no longer have access to page-wide variables. Surely enough, looking through the documentation for shortcode, that is the case. I need to use .Page.Params
. This seems to have changed because I see forum posts and blog posts that have conflicting information.
Finally, I settle on:
{{if .Page.Params.mermaid}}
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true, theme:"dark"});</script>
{{end}}
<div class="mermaid">
{{.Inner}}
</div>
Checking quickly on one of the other pages on my blog, I see that mermaid doesn't appear in their html
, which is a good sign.
Stretch Goal 2: Don't load and initialize mermaid more than once
In reality, the best practice would be to only load the minified js and initialize the mermaid object once per page rather than for each time I use the shortcode. This time, it looks like in order to do this I have no choice but to fork the theme (or I suppose submit a PR), so that's what I'll do.
My first approach is to take the content of that if block and move it into the template for posts in my theme.
{{if .Page.Params.mermaid}}
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true, theme:"dark"});</script>
{{end}}
So in my themes/m10c/layouts/_defaults/baseof.html
, I put this in at the bottom of the header. It turns out, this worked just as I had hoped.
Anyway, I hope someone out there found this useful and this doesn't become one of those outdated blog posts too.