/ #blog #R 

Setting up your blog with RStudio and blogdown III: modify your theme

This is Part III of my series of posts about how to setup you blog with RStudio and blogdown. The other parts are:

  • Part I about to setup the blog using Hugo, RStudio and blogdown
  • Part II explains my workflow of creating new posts.
  • Part III (this one) how to modify the theme.

In this post I am going to show how I modified the original theme casper-two that I use for my blog. It is a great theme, but for sure you would like to change the menus, the appearance, the structure of the information in the blog, etc.

Content:

Hugo internals

But before that we have to learn a little bit about how Hugo and our theme works underneath our blog. If you have a look the folder where your blog is, you will find this structure

The last directory (hugo-casper-two) contains the theme files and has this structure:

The folder layouts contains a lot of html files in HUGO format that specify how our pages are going to be rendered. Each file is a mixture of html and HUGO variables. For example, this is how the index.html looks like

{{ partial "head" . }}

<body class="home-template"><div class="site-wrapper">

{{ partial "header" . }}

<main id="site-main" class="site-main outer" role="main">
  <div class="inner">
    <div class="post-feed">
      {{ partial "post-list-main" . }}
    </div>

    <nav class="pagination" role="navigation">
      {{if .Paginator.HasPrev}}
          <a class="newer-posts" href="{{ .Paginator.Prev.URL }}">&larr; 
               <span class="hide">Next Posts</span></a>
      {{end}}
      <span class="page-number"><span class="hide">Page {{ .Paginator.PageNumber }} 
            of {{.Paginator.TotalPages}}</span>&nbsp;</span>
      {{if .Paginator.HasNext}}
          <a class="older-posts" href="{{ .Paginator.Next.URL }}">
          <span class="hide">Previous Posts</span> &rarr;</a>
      {{end}}
    </nav>
  </div>
</main>

{{ partial "footer" . }}
</div>{{ partial "js" . }}</body></html>

Each {{ }} command tells Hugo to do something. In particular the first one ({{ partial "head" . }}) includes in that position the partial head.html which is also in the layouts folder. Then it comes some HTML, another partial header.html, etc. You can also put controls/functions for the final code (see for example the if control {{if .Paginator.HasPrev}}) or output some variables in the code depending on the context ({{ .Paginator.Prev.URL }}), etc. Variables and parameters are preceded by a dot. For example .Site.BaseURL` is the base URL for the site.

Have a look at how Hugo manages the content in the templates in the Hugo webpage.

Modifying the template requires to changing the files for the different layouts. But instead of modifying the original template files in the themes folder, it is better to follow the advise here and replicate the layouts structure outside the themes folder. Hugo will read first our own layout folder and then the layout folder in the original theme. For example, in this post we are going to modify index.html, js.html, etc. And create new layouts post-list-main.html or shortcodes like yt.html. So your final blog folder will look like this.

Changing the main menu

This is really easy. Go to your config.toml and you will see a number of [[menu.main]] items in it. Change them to link to

  • Category pages, showing all the posts in a category. Like this:
[[menu.main]]
  name = "Publications"
  url = "/categories/publications"
  weight = 150
  • Individual pages. A link to a particular page/post in your blog
[[menu.main]]
  name = "About"
  url = "/about/"
  weight = 94
  • External pages. A link to a external page not in your blog
[[menu.main]]
  name = "My University"
  url = "http://www.uc3m.es"
  weight = 32

The weight controls the order in which they appear. Higher weights mean they will appear in the leftmost positions.

Google Analytics

If you want to use Google Analytics for your blog, you can do it by specifying your Google Analytics tracking id in the config.toml

googleAnalytics = "UA-XXXXYYZZY-X"

It works great, but there is something that I didn’t like: the tracking works even in your production environment when you are running the blogdown::serve_site(). This means that when you are writing a post, updating locally your pictures, etc., and you navigate your blog in Rstudio, it will count as visits to your blog in Google Analytics.

We are going to modify the theme so that only visits from others are counted in Google Analytics. To do that we are going to copy the file js.html to our local layouts folder. If you edit it you will see that the Google Analytics script is the following part:

{{ with .Site.Params.googleAnalytics }}
  {{ "<!-- Google Analytics -->" | safeHTML }}
  <script>
    var _gaq=[['_setAccount','{{ . }}'],['_trackPageview']];
    (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
    g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
    s.parentNode.insertBefore(g,s)}(document,'script'));
  </script>
{{ end }}

But we are going to tell Hugo to include this script only if the webpage is in the final domain, that is, if we build the final deployment version of our blog. To do that you have to change the above code to

{{ $baseurl := printf "%s" .Site.BaseURL }}

{{ if eq $baseurl "http://estebanmoro.org/" }}

{{ with .Site.Params.googleAnalytics }}
  {{ "<!-- Google Analytics -->" | safeHTML }}
  <script>
    var _gaq=[['_setAccount','{{ . }}'],['_trackPageview']];
    (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
    g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
    s.parentNode.insertBefore(g,s)}(document,'script'));
  </script>
{{ end }}

{{ end }}

Note the if before the script that tells Hugo to include it only if the base URL for the site coincides with the domain (in this case my domain http://estebanmoro.org)). I have tried other solutions mentioned here and here, but this is the only that worked for me.

Math equations

One of the best thing (at least for me) of R markdown is the ability to type directly into \(\LaTeX\). This way I can type

$\int_0^\infty f(x) dx$

and it will be rendered as

\(\int_0^\infty f(x) dx\)

However, the casper-two theme does not come with the MathJax javascript library which displays mathematical equations in the browser. To include it we have to add the javascript somewhere in the head of all pages. Let’s do it using the js.html. Once again, edit the file in your layouts folder and add the following line at the top:

<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>

Altmetrics

For those of us that work in academia, we use the number of citations or alternative ways to measure the impact of a paper. One of them is Altmetric which is particularly useful to track who is talking about your research in social media, newspapers, blogs, etc.

Here is how the Altmetric page looks for one of my papers:

On the top left you can see the Altmetric bagde for your paper, which is a sum of the mention in news outlets, blogs, Twitter users, etc.

Altmetric provides free javascript to embed that badge in your webpage (in a lab or personal homepage), so I decided to use it in each of my papers. Here are the instructions from the Altmetric page

  • Firstly, we have to include the Altmetric javascript in our pages. Edit the js.html again to include:
<script id="altmetric-embed-js" type="text/javascript"
src='https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js'></script>

Note that I have included the id="altmetric-embed-js" part to the script. The instructions at Altmetrics do not mention the id.

  • Get the DOI for your article. The DOI is a universal identification number for each paper. Most likely you can get it from the journal publication page. For the my paper is 10.1371/journal.pone.0195750

  • If you want to include your badge in your R/markdown:
<div class='altmetric-embed' data-badge-type='donut'
data-badge-popover="right"
data-doi="10.1371/journal.pone.0195750"></div>
If everything worked you will see something like this:

You can modify and build your own badge. Check all the options here.

Custom CSS

You can also modify the style of your webpage through CSS. Although the CSS file of casper-two theme is buried in the theme folder, there is a possibility to override some of the styles by using a custom CSS. To do that you have to edit the customCSS = [""] in your config.toml. In my case I added a file overrides.css to the static/css folder and change the config.toml file to:

customCSS = ["css/overrides.css"]

Here are the contents of my overrides.css in case you want to use it. It basically changes the title font size and weight:

.site-title {
    font-weight: 200;
    font-size: 7rem;
}
.site-header:before {
    background: rgba(0, 0, 0, 0.5);
}

Show only some posts in the main page

On the things that I missed in my new blog is the ability to show in the main page only the posts that I want. For example, I have a list of publications that I update regularly but I don’t want it to show in the main page. Or you could have a tutorial and you only want it to show up in a specific part of your blog.

To do that we are going to add a new parameter (by post) hidden that tells Hugo to show the post in the main page (hidden: false) or not (hidden: true). Edit your post and include it in the post YAML as it is done here:

---
title: List of Publications
author: Esteban Moro
date: '2019-01-31'
slug: list-of-publications
hidden: true
categories:
  - Publications
image: "img/Publications.jpg"
---

Next thing is to modify the layouts of your theme so that when your blog is rendered by Hugo it does not show posts with hidden: true in your main page.

The way I did it is the following: - Copy the post-list.hml partial in the layout/partials folder of the theme to your own layout/partials. - Rename the post-list.html to post-list-main.html. The reason for this is that we are going to change the latter by we want the theme to use the former when necessary. - Edit the post-list-main.html it looks like this

{{ $paginator := .Paginate (where .Data.Pages.ByDate.Reverse "Type" "post") }}
{{ range $paginator.Pages }}
{{ if not .Params.hidden }}

<article class="post-card post"> 
    {{if .Params.image}}
    <a class="post-card-image-link" href="{{ .Permalink }}">
      <div class="post-card-image" style="background-image: url({{.Params.image | absURL}})"></div>
    </a>
    {{else}}
    <a class="post-card-image-link" href="{{ .Permalink }}">
    <div class="post-card-image" style="background-image: url({{$.Site.BaseURL}}defimg/{{ index (seq 7 | shuffle) 0 }}.jpg)"></div>
    </a>    
    {{end}}

    <div class="post-card-content">
      <a class="post-card-content-link" href="{{ .Permalink }}">
          <header class="post-card-header">
              {{if .Params.tags }}<span class="post-card-tags">{{ range $index, $tag := .Params.tags }}
              #{{$tag}} {{end}} </span>
              {{ end }}
              <h2 class="post-card-title">{{.Title}}</h2>
          </header>
          <section class="post-card-excerpt">
              {{ if .Description }} 
                <p>{{ .Description | markdownify }}</p>
              {{else}}
                <p>{{ .Summary | plainify | safeHTML }}{{ if .Truncated }} ... {{end}} </p>
              {{end}}
          </section>
      </a>

      <footer class="post-card-meta">
          <img class="author-profile-image" src="{{ (.Params.authorAvatar | default .Site.Params.authorAvatar) | absURL}}" alt="Author" />
          <span class="post-card-author"><a href="/">{{.Params.author | default .Site.Params.author}}</a></span>
      </footer>
    </div>
</article>
{{ end }}

{{ end }}

Note the if in the third line that only renders each of the posts if .Params.hidden is not true.

Finally we have to modify index.html (once again copy it to your local layouts folder) to tell Hugo to use the new post-list-main.html instead of the old post-list.html. If you edit index.html change these lines:

  <div class="post-feed">
      {{ partial "post-list" . }}
  </div>

to this one:

  <div class="post-feed">
      {{ partial "post-list-main" . }}
  </div>

And you are done. All posts with hidden: true in their YAML will not be shown in the main page. But they will appear in the categories, recommended posts, etc. If you don’t want them to show in those pages, edit the partials for each of them to include the {{ if not .Params.hidden }}.

Fixing Youtube

Hugo has a number of shortcodes to embed tweets, videos, gist, etc. For example to embed a youtube video at https://www.youtube.com/watch?v=w7Ft2ymGmfc you simply have to put this in your R/markdown:

{{< youtube w7Ft2ymGmfc >}}

However, this does not work with the casper-two theme, an issue that has been reported in the theme homepage, but has not been fixed yet.

To fix it, you can use the solution proposed by Softinio in the opened issue page. It is very simple but requires to create a Hugo shortcode:

  • Create a new file named yt.hml in your layouts/shortcodes folder.
  • Put the following code in that file:
<iframe src="https://www.youtube.com/embed/{{ index .Params 0 }}?start={{ index .Params 1 }}"
style="position: absolute; top: 0; left: 0; width: 560; height: 315;"
allowfullscreen frameborder="0" title="YouTube Video"></iframe>
  • You can now use the new shortcode in your R/markdown like this:

{{< yt w7Ft2ymGmfc >}}

Summary

That’s it. It’s a long post (sorry), but I hope you might find it useful. Let me know if there is a better way to do these modifications! 🚀

Author

Esteban Moro

Professor at Universidad Carlos III de Madrid and MIT Medialab. Working on Complex Systems, Social Networks and Urban Science.