Hugo development

Custom Shortcodes in Hugo

December 7, 2020

I’m currently writing on a post about a cool new tool I recently found GitGraph.js. In the article, I wanted to show some examples of what you can do with it and how it' renders. But of course, this did not work out-of-box with Hugo. While investigating how to do Git graphs I found that MermaidJS had some primitive support for GitGraph.js.

It’s is not a complicated task to include support for GitGraph.js using Hugo shortcodes, there is however some caveats to it. Below I have written a guide to include support for GitGraph.js and MermaidJS in your Hugo site

Base Setup

While I’m writing this article, I’m using this configuration.

Hugo v0.78.2/extended.

Theme simple-blog by 10mohi6 (with personal customizations)

    $ > hugo version
    Hugo Static Site Generator v0.78.2/extended darwin/amd64 BuildDate: unknown

File Structure and Purpose

For me, it’s essential not to touch the original theme’s files, so I overwrite them in my file structure.

Relative Path in the RepoFile Description
config/_default/config.tomlHugo Static Site Generator global configuration file See Hugo-CI
archetypes/default.mdDefault Configuration used to create a new Post
data/git-example.yamlGit Graph Commit Information Data file
layouts/partials/script.htmlPartial template that enables additional imports for JavaScript and CSS
layouts/shortcodes/mermaid.htmlShort Code to use for MermaidJS
layouts/shortcodes/gitgraph.htmlShort Code to use for GitGraph.js Inclusion

Short Code for MermaidJS

Basic Configguration

Include Custem JavaScript and CSS Files

Let us make a few changes in the config.toml to include a few items that indicate all the required js and CSS files to be included into the static site to get the mermaidJS diagrams working.

    # config.toml
    [params]
    mermaidJS = '<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.5.2/mermaid.min.js" integrity="sha256-y9inIfeUjJK2oCN6/ER6D/iC661lxlvgYGHt6WnZ/xk=" crossorigin="anonymous"></script>'

Conditionally Enable MermaidJS on posts

Now, we need to include the Javascript configured above in the config.toml so that it can be included while generating the static site if required.

    <!-- 
    file: layouts/partials/script.html

    We selectively check if the post asks for MermaidJS to be included or not and include the JS required, only when that parameter is set to true. This is done to ensure that we don't include unwanted js and slow down the page load time.
    -->
    {{- if and ( .Params.mermaidJS.enable ) (or .IsPage .IsHome) -}}
    {{ .Site.Params.mermaidJS | safeHTML }}
    {{- end }}

Include it in the site

At the bottom of the themes/simple-blog/layouts/_default/single.html or you can do as I did and copy it to layouts/_default/single.html and add the following line to the file you wish to use.

    ...
    {{ partial "scripts.html" . }}

Create the shortcode files

layouts/shortcodes/mermaid.html should look as follow

    <!-- file: layouts/shortcodes/mermaid.html -->
    <div class="mermaid">
        {{.Inner}}
    </div>

Setup Default options for ShortCode configuration

Configure the archetypes/default.md file to include the default values for shortCode configuration

    ---
    title: "{{ replace .Name "-" " " | title }}"
    date: {{ .Date }}
    draft: true
    mermaidJS:
        enable: false
    ---

Consuming MermaidJS Short Codes

Now we can create a new Post using the Hugo command-line option.

    $> hugo new content/posts/example.md                      
    content/posts/example.md created

    ---
    title: "Example"
    date: 2020-07-03T17:14:38+05:30
    draft: true
    mermaidJS:
        enable: true
    ---

    # Mermaid example
    { {< mermaid >}}  <-- notice the space between the two {{ it should not be there!
    sequenceDiagram
    A->B: Normal line
    B-->C: Dashed line
    C->>D: Open arrow
    D-->>A: Dashed open arrow
    { {</ mermaid >}} <-- notice the space between the two {{ it should not be there!

and you’ll get

Mermaid example

sequenceDiagram A->B: Normal line B-->C: Dashed line C->>D: Open arrow D-->>A: Dashed open arrow

Futher reading

Short Code for GitGraph.js

While exploring the possibilities of mermaidJS, I noticed they have primitive support for the GitGraph.js that can be used to render a commit-graph of your git logs. However, it is very primitive and not extendable yet as it is still in the experimental phase. But there is an excellent alternative in the form of GitGraph.js.

Including this one took a bit more effort than including the mermaidJS. Since the integration wasn’t that simple, and I wanted it to be backed by a data file instead of hard-coding the commit-graph’s contents in the markdown document of the Blog.

Basic configuration

Include Custom JavaScript and CSS Files

Same as we did while performing mermaidJS integration, we will include a configuration file record to have javascript and CSS files that will bring in the GitGraph.js support.

    [params]
    gitgraph = '<script src="https://cdn.jsdelivr.net/npm/@gitgraph/js" crossorigin="anonymous"></script>'

Conditionally Enable GitGraph on posts

    <!-- 
    file: layouts/partials/script.html

    We selectively check if the post asks for MermaidJS to be included or not and include the JS required only when that parameter is set to true. This is done to ensure that we don't include unwanted js and slow down the page load time.
    -->
    {{- if and ( .Params.gitGraph.enable ) (or .IsPage .IsHome) -}}
    {{ .Site.Params.gitGraph. | safeHTML }}
    {{- end }}

Setup Default options for ShortCode configuration

Configure the archetypes/default.md file to include the default values for shortCode configuration

    ---
    title: "{{ replace .Name "-" " " | title }}"
    date: {{ .Date }}
    draft: true
    gitGraph:
        enable: false
    ---

Setup data Rendering using Data Files and ShortCode

Hugo has an incredible concept called data templates that lets you use toml, YAML or JSON file as a source of data and use them in your shortcode templates as you see fit. To activate this, all you need to do is create the following.

Create a file data/example.toml, and you are done. Now you can access the data from the file using example arg and combine it with a magic sauce {{ $dataFile := index .Site.Data "example" }}

    <div id="graph" class="svg-container"></div>

    {{ $arg := .Get 0 }}
    {{ $dataFile := index .Site.Data $arg }}

    <script>
    window.onload = (event) => {
        const graphContainer = document.getElementById("graph");
        var template = {
        colors: ["#6963FF", "#47E8D4", "#6BDB52", "#E84BA5", "#FFA657"],
        arrow: { size: 5, color: null, offset: -1.5 },
        branch: {
            color: "#e0ebeb",
            lineWidth: 3,
            mergeStyle: "straight",
            spacing: 30,
            label: {
            display: true,
            bgColor: "white",
            font: "normal 10pt 'Fira Mono', monospace",
            borderRadius: 5,
            },
        },
        commit: {
            spacing: 50,
            hasTooltipInCompactMode: true,
            dot: {
            size: 5,
            strokeWidth: 1,
            strokeColor: "#e0ebeb",
            font: "normal 10pt 'Fira Mono', monospace",
            },
            message: {
            display: true,
            displayAuthor: false,
            displayHash: true,
            color: "black",
            font: "normal 10pt 'Fira Mono', monospace",
            },
        },
        tag: {},
        };  
        

        var gitGraph = window.GitgraphJS.createGitgraph(graphContainer, {
        author: "Jes Struck <mail@jesstruck.dk>",
            template: template,
            orientation: "vertical",
        layout: "responsive",
        svgClasses: ["svc-content"],
        });
        window.gitGraph = gitGraph;
        
            
        
        var g = window.gitGraph;

        function createNewGitBranch(data){
        /**
        * Check if there is a property for the window object that matches the
        * name of the branch in question. If it is present, that means a branch
        * has already been created, and there is nothing else that needs to be done.
        * 
        * However, if the is no property matching the branch name is present, then
        * this is a new branch and needs to be created.
        * 
        * You are free to customize the way these properties are managed at the
        * window lever for better optimization.
        */
        var parentBranch = data.meta.parent;
        var branchName = data.meta.name

        if ( window.hasOwnProperty(branchName) ) {
            return branchName;
        }

        if ( parentBranch === "" ) {
            var branch = g.branch(branchName);
        } else {
            var branch = window[parentBranch].branch(branchName);
        }
        window[branchName] = branch;
        return branchName
        }

        /**
        * Conditionally add comments and other branch level components so that the
        * graph is built and rendered on the UI.
        */
        {{ range $branch := $dataFile.gitGraph }}
        var branchName = createNewGitBranch({{ .branch }});
        {{ range $commit := .branch.commit }}
        window[branchName].commit({{ . }});
        {{ end }}
        {{ if index .branch "merge" }}
        var baseBranch = {{ .branch.merge.base }};
        var mergeCommit = {{ .branch.merge.commit }};
        window[baseBranch].merge(window[branchName], mergeCommit);
        {{ end }}
        {{ if index .branch "tag" }}
        var branchToTag = {{ .branch.tag.branch }};
        var tagVersion = {{ .branch.tag.version }};
        window[branchToTag].tag(tagVersion);
        {{ end }}
        {{ end }}
    }  
    </script>

Create an Example Data File

    # data/git-example.yaml
    gitGraph:
    - branch:
        meta:
            parent: ""
            name: master
        commit:
            - Initial commit
            - More work 
            - Introduce responsive layout
            - "Add media query support."
            - "Add parameterized template override."

Consuming GitGraph shortcode

   ---
    title: "Example"
    date: 2020-07-03T17:14:38+05:30
    draft: true
    gitGraph:
        enable: true
    ---
    { {< gitgraph "git-example" >}} <-- Skip the space between the two {{

Which will render a GitGraph.js like the one below.

GitGraph example

Tagged: #Hugo #javascript #GitGraphJS #MermaidJS