---
title: "Extending Contentful"
date: 2020-05-12T17:25:55+08:00

categories:
    - "guide"
tags: 
    - "theme"
    - "extending"
---

= Extending Contentful
2020-05-12

Extending a Hugo theme is nothing new and a https://gohugo.io/content-management/sections/[few] http://hugocodex.org/add-ons/[places] provide a place for the most common extensions.
In this post, I'll be listing a few personal recipes I've always used for extending a Hugo theme.
Though this only applies specifically to Contentful and may need some tweaking when applying it other themes.




== Customizing your `<head>`

Let's start with the most basic and perhaps most useful customization: modifying the `<head`>.
This is useful for adding your own CSS and JavaScript files, changing certain metadata, or adding icons.

First, copy the `head` partial from the theme (`theme/contentful/layouts/partials/head.html`) to your own (`layouts/partials/head.html`).
We're simply taking advantage of https://gohugo.io/templates/lookup-order/[Hugo's lookup order] where we've override the `head` partial with our own copy.

Then, feel free to add your own (or others') scripts and stylesheets, https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML[icons and other metadata], or whatever suitable things.

In my case, I often use certain JavaScript libraries like https://www.mathjax.org/[MathJax] for mathematical typesetting, https://prismjs.com/[Prism] for syntax highlighting, and https://github.com/francoischalifour/medium-zoom/[medium-zoom] for interactive image zooms.

Here's the modified code.
(The example code is snipped for brevity.)

```go
<!--snip-->

{{- /* MathJax */ -}}
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" defer src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>

{{- /* Prism.js */ -}}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/themes/prism-tomorrow.min.css" type="text/css">
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/components/prism-core.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/plugins/autoloader/prism-autoloader.min.js">
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/plugins/keep-markup/prism-keep-markup.min.js">

{{- /* medium-zoom */ -}}
<script defer src="https://cdn.jsdelivr.net/npm/medium-zoom@1.0.5/dist/medium-zoom.min.js"></script>
<script>window.addEventListener('load', () => mediumZoom('article img', { 'background': 'rgba(0, 0, 0, 0.75)' }))</script>
```

Since most of the JavaScript libraries used here are not really a requirement (except for MathJax for mathematical typesetting), I've set them to be loaded at the end of the page loading with https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script[`defer` attribute].
If you have an inline script, you can simply wrap it in an event listener for page loading (`window.addEventListener("load", your_function_goes_here)`).

If you want document-specific libraries, you have to pass some raw HTML through the parser of the document.
For Goldmark, the default Markdown parser starting https://gohugo.io/news/0.60.0-relnotes/[Hugo v0.60.0], blocks raw HTML by default and you can disable it by setting `markup.goldmark.renderer.unsafe` to `true`.

For Blackfriday, it parses even the raw HTML just fine.
Though, you have to set it as the default Markdown parser.

For https://asciidoctor.org/[Asciidoctor], you can use https://asciidoctor.org/docs/user-manual/#passthroughs[passthroughs] to get raw HTML through.




== Twitter cards

This will add https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started[Twitter cards] for your webpages.
(Be sure to copy the `head` partial first in your own layout folder.)

Thankfully, Hugo already has https://gohugo.io/templates/internal/#twitter-cards[an internal template for Twitter cards].
Simply add  `{{- template "_internal/twitter_cards.html" . -}}` somewhere in your own copy.
(For reference, https://github.com/gohugoio/hugo/blob/25a6b33693992e8c6d9c35bc1e781ce3e2bca4be/tpl/tplimpl/embedded/templates/twitter_cards.html[here's the source code for the internal template].)

You could also roll your own Twitter cards but I recommend to modify the internal template instead fitting your specific needs.
(Copy the internal template from the given link, create it as a partial in `layouts/partials/twitter_cards.html`, modify it, and insert the template with `{{- partial "twitter_cards.html" -}}`.)




== Open Graph protocol

Next up, we're implementing https://opengraphprotocol.org/[Open Graph protocol] for our webpages.
Commonly used for making suitable format when sharing the content on certain sites like Facebook.
(Be sure to copy the `head` partial first in your own layout folder.)

Similar to Twitter cards, Hugo has https://gohugo.io/templates/internal/#open-graph[an internal template for this].
Simply add  `{{- template "_internal/opengraph.html" . -}}` somewhere in your own copy.
(For reference, https://github.com/gohugoio/hugo/blob/25a6b33693992e8c6d9c35bc1e781ce3e2bca4be/tpl/tplimpl/embedded/templates/opengraph.html[here's the source code for the internal template].)

If you want more control and customized version of the output, I recommend to copy the internal template and create a partial (e.g., `layouts/partials/opengraph.html`) and modify it.




== An archive page

This will add an archive page similar to archive pages https://davidtranscend.com/archives/[like] https://lukesmith.xyz/blogindex.html[these].

```go
{{- define "main" -}}

<h1>{{ .Title }}</h1>

{{ .Content }}

<hr>

{{- /* Creating a section that lists out regular pages by year */ -}}
{{ range $.Site.RegularPages.GroupByPublishDate "2006" }}
    {{- /* Skip regular pages with an invalid creation date string. */ -}}
    {{- /* This is convenient if we want to exclude certain posts to be listed by giving no value to `date` in the frontmatter. */ -}}
    {{- /* We will also exclude hidden pages. */ -}}
    {{ if ne .Key "0001" }}
        <section data-year="{{ .Key }}">
            <h2 id="{{ .Key }}">{{ .Key }}</h2> 
            <ul>
            {{- range where .Pages "Params.hidden" "!=" true -}}
                <li>
                    <date>{{ .Date.Format "2006-01-02" }}</date> -
                    <a aria-label="{{ .Title }}" href="{{ .Permalink }}">{{ .Title }}</a>
                </li>
            {{- end -}}
            </ul>
        </section>
    {{- end }}
{{ end }}

{{- end -}}
```

We will simply add this as a layout in our customized theme.
Let's call it `archives` so we have to add a file in `layouts/_default/archives.html` then set a page of our project with the `layout` key in the frontmatter.

We want the archives page to be accessed at `$.Site.BaseURL/archives` so we'll simply create `archives.adoc` (https://gohugo.io/content-management/formats/#list-of-content-formats[any valid content files with certain file extensions can do], I'm using https://asciidoctor.org/[Asciidoctor]) with the following example content.

```asciidoctor
---
title: "Archives"
layout: "archive"
---

= Archives

This is the archives of the century.
```




== Configurable social media links

Most themes offer quick social media links with site configuration.
However, it is only limited to popular media sites such as Facebook, Twitter, Instagram, GitHub, etc.

To get around this, we'll make use of https://gohugo.io/templates/data-templates/[data templates].
Let's create a quick game plan how does it work.

The data is a top-level dictionary/object with each key contains an object with the following fields.

* `url` is the... contact link itself and it is required to have it.

* `name` is the text to appear in the output.
Also required to have.

* `weight` is an integer similar to how Hugo sorts the pages with the lower weight having high precedence;
if this key is absent, it will be interpreted as 0.

And here's the data example in TOML which is placed in `data/contact.toml`.

```toml
[github]
    name = "GitHub"
    url = "https://github.com/foo-dogsquared"

[gitlab]
    name = "Gitlab"
    url = "https://gitlab.com/foo-dogsquared"

[keybase]
    name = "Keybase"
    url = "https://keybase.io/foo_dogsquared"
    weight = -1

[twitter]
    name = "Twitter"
    url = "https://twitter.com/foo_dogsquared"
```

I want my Keybase profile to appear first than anything else for whatever reason so the `weight` key is set to -1.

With this data, we can then create a template out of it.
I've put the following template in a partial named `contacts` (i.e., `layouts/partials/contacts`).

```go
<address>
{{- range (sort $.Site.Data.contact "weight" "asc") -}}
| <a rel="me" href="{{ .url }}">{{- .name -}}</a> |
{{- end -}}
</address>
```

A suggestion (and an exercise) for extending this is to create image links.
Maybe add another key named `image` that accepts either URL.
The `name` would now be the image alternative text.
