A Little Bit of This, A Little Bit of That

Justin Yang


Administration     7 min   source

I’ve had a few days to get used to Blogdown, Hugo, and Xmin now, and of course I immediately took to tinkering. This is just a short post to highlight some of the changes I’ve made and to whom I owe thanks.

Last Things, First (Footnotes, That Is)

There is nothing inherently wrong with how Hugo Xmin handles footnotes and some might go so far as to say it is elegant. Still, based on personal preference, I have wrapped my footnotes with littlefoot.js which, for, me, has nicer footnotes which are a bit easier to deal with, especially on mobile. I also didn’t want a super lengthy front page so it was nice to hide my chatty footnotes that way.

I added this to my foot_custom.html file after seeing this post on the Hugo forums. I’m very aware that this makes the page potentially load more slowly, so I’m keeping an eye out on performance.

<link rel="stylesheet" href="https://unpkg.com/littlefoot/dist/littlefoot.css" />
<script src="https://unpkg.com/littlefoot/dist/littlefoot.js" type="application/javascript" ></script> 
<script type="application/javascript"> littlefoot.default() </script>

Better Publication Lists Through Automation

One big goal for this iteration of my personal academic webpage was to minimise the time I spend manually updating content. I was very inspired by Thomas Hackl’s work on this but I changed things up a little to suit my purpose as follows:


publications <- as_tibble(get_publications("o-MsbBYAAAAJ")) %>%
  arrange(desc(year)) %>%
    author = str_replace_all(author, "([A-Z]) ([A-Z]) ", "\\1\\2 "),
    author = str_replace_all(author, ", \\.\\.\\.", " et al"),
    author = str_replace_all(author, "JC Yang", "**JC Yang**"),
    author = str_replace_all(author, "J Yang", "**J Yang**"),
    title = str_replace_all(title, ":.+", "")
  ) %>%
    citation = paste0(
      ". ",
      " ",
      ". ",
      " ",
  ) %>%

publications %>% knitr::kable("html", col.names = NULL)

Frankenstein Solutions for Peer Reviews

I also wanted to be able to share an unordered list of publications for which I have served as an invited referee. I use Publons to track these and I push from Publons to ORCID, so when I came across the rorcid package, I knew I was onto something good! I managed to make this work (probably inelegantly) with a lot of help from Clarke’s great walk through of rorcid and gorkang’s solution to the issue of errors when using rcrossref::cr_journals. 1

reviews <-
  orcid_peer_reviews("0000-0003-2881-4906")[[1]]$group$`peer-review-group` %>%
  map_dfr(pluck, "peer-review-summary") %>%
  as_tibble() %>%
  clean_names() %>%
  select(review_group_id) %>%
  distinct() %>%
  mutate(issn = stri_enc_toutf8(str_replace(review_group_id, "issn:", ""))) %>%
get_title_from_issn <- function(issn) {
    error = function(e) {
journal_names <-
  reviews %>% purrr::map(~ get_title_from_issn(.x)) %>% unlist() %>% as_tibble() %>% drop_na() %>% arrange(value) %>% mutate(value = str_squish(value)) %>% pivot_wider(names_from = value)

cat(paste0("- ", names(journal_names)), sep = "\n")

I’ll Show You Mine, You Show Me Yours

I wanted an easy way to share my presentations so users could view them on mobile devices easily. Moving forward, I’d really like to make more use of reveal.js because it’s just really nice and slick but I might rely on Beamer for more technical presentations (and anyway, I needed to a way to share PDFs of older presentations when I used a lot more PowerPoint and Stata…). So I had two things to accomplish, really.

For reveal.js, I came across taipapa’s shortcode for embedding reveal.js presentations, which I shamelessly borrowed for this page by adding the following shortcode:

<iframe src="{{.Get 0}}" width="1000" height="600" frameborder="0" allowfullscreen="allowfullscreen" allow="geolocation *; microphone *; camera *; midi *; encrypted-media *"></iframe>

For PDFs, I wanted something relatively light and which could render decently on mobile devices. I ended up settling on a solution using PDF.js which I came across from anvithks, but I didn’t want to have to keep the javascript updated, so I ended up using CDNJS for PDF.js with minor modifications to the shortcode:

<script type="text/javascript" src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.min.js'></script>
#the-canvas {
  border: 1px solid black;
  direction: ltr;
  width: 100%;
  height: auto;
  display: none;

#paginator {
  display: none;
  text-align: center;
  margin-bottom: 10px;

#loadingWrapper {
  display: none;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 350px;

#loading {
  display: inline-block;
  width: 50px;
  height: 50px;
  border: 3px solid #d2d0d0;;
  border-radius: 50%;
  border-top-color: #383838;
  animation: spin 1s ease-in-out infinite;
  -webkit-animation: spin 1s ease-in-out infinite;

@keyframes spin {
  to { -webkit-transform: rotate(360deg); }
@-webkit-keyframes spin {
  to { -webkit-transform: rotate(360deg); }

<div id="paginator">
    <button id="prev">Previous</button>
    <button id="next">Next</button>
    &nbsp; &nbsp;
    <span>Page: <span id="page_num"></span> / <span id="page_count"></span></span>
<div id="embed-pdf-container">
    <div id="loadingWrapper">
      <div id="loading"></div>
    <canvas id="the-canvas"></canvas>

<script type="text/javascript">
window.onload = function() {
// If absolute URL from the remote server is provided, configure the CORS
// header on that server.
var url = '{{ .Get "url" }}';

var hidePaginator = "{{ .Get "hidePaginator" }}" === "true";
var hideLoader = "{{ .Get "hideLoader" }}" === "true";
var selectedPageNum = parseInt("{{ .Get "renderPageNum" }}") || 1;

// Loaded via <script> tag, create shortcut to access PDF.js exports.
var pdfjsLib = window['pdfjs-dist/build/pdf'];

// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.worker.min.js';

// Change the Scale value for lower or higher resolution.
var pdfDoc = null,
    pageNum = selectedPageNum,
    pageRendering = false,
    pageNumPending = null,
    scale = 3,
    canvas = document.getElementById('the-canvas'),
    ctx = canvas.getContext('2d'),
    paginator = document.getElementById("paginator"),
    loadingWrapper = document.getElementById('loadingWrapper');

// Attempt to show paginator and loader if enabled

 * Get page info from document, resize canvas accordingly, and render page.
 * @param num Page number.
function renderPage(num) {
  pageRendering = true;
  // Using promise to fetch the page
  pdfDoc.getPage(num).then(function(page) {
    var viewport = page.getViewport({scale: scale});
    canvas.height = viewport.height;
    canvas.width = viewport.width;

    // Render PDF page into canvas context
    var renderContext = {
      canvasContext: ctx,
      viewport: viewport
    var renderTask = page.render(renderContext);

    // Wait for rendering to finish
    renderTask.promise.then(function() {
      pageRendering = false;
      if (pageNumPending !== null) {
        // New page rendering is pending
        pageNumPending = null;

  // Update page counters
  document.getElementById('page_num').textContent = num;

 * Hides loader and shows canvas
function showContent() {
  loadingWrapper.style.display = 'none';
  canvas.style.display = 'block';

 * If we haven't disabled the loader, show loader and hide canvas
function showLoader() {
  if(hideLoader) return
  loadingWrapper.style.display = 'flex';
  canvas.style.display = 'none';

 * If we haven't disabled the paginator, show paginator
function showPaginator() {
  if(hidePaginator) return
  paginator.style.display = 'block';

 * If another page rendering in progress, waits until the rendering is
 * finished. Otherwise, executes rendering immediately.
function queueRenderPage(num) {
  if (pageRendering) {
    pageNumPending = num;
  } else {

 * Displays previous page.
function onPrevPage() {
  if (pageNum <= 1) {
document.getElementById('prev').addEventListener('click', onPrevPage);

 * Displays next page.
function onNextPage() {
  if (pageNum >= pdfDoc.numPages) {
document.getElementById('next').addEventListener('click', onNextPage);

 * Asynchronously downloads PDF.
pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
  pdfDoc = pdfDoc_;
  var numPages = pdfDoc.numPages;
  document.getElementById('page_count').textContent = numPages;
  // If the user passed in a number that is out of range, render the last page.
  if(pageNum > numPages) {
    pageNum = numPages

  // Initial/first page rendering


Shiny, Pretty Things

Hugo-Academic makes great use of Font Awesome and Academicons, and though I’m aware that they are a bit superfluous and self-indulgent, I added this here anyway because I liked them. Again, another performance hit, so I’m keeping my eye on things, but I think it’s not too bad, right? All I had to do was added these to head_custom.html:

<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.0/css/all.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/academicons/1.9.2/css/academicons.min.css" rel="stylesheet">

Talk Nerdy to Me

Finally, I didn’t want too much clutter on my pages, but I didn’t mind having a dialogue with people with respect to these notes and with presentations. I came across María Paula’s great guide to getting utteranc.es set up and it worked like a charm! All I had to do was added the following to my notes and presentations pages:

{{ if not .Params.disable_comments }}
<script src="https://utteranc.es/client.js"

Final Thoughts

This has been a huge learning opportunity and it’s been so much fun! I’ve learned a lot just by trying things and seeing what works and what doesn’t. 2 Of course, I would be nowhere without Yihui Xie and the great blogdown book! I hope to keep learning and making this little corner of the internet my own. Thanks for being part of my journey!

  1. Fun fact, I got all caught up in the great Crossref outage on March 24, and couldn’t knit that page after making some updates! I thought I broke the API or something, but I was relieved to find out it wasn’t my fault!↩︎

  2. I’m sure there are still many things that don’t work, so please, if you come across something like that, just let me know!↩︎