playing with quatro

this is STILL in beta
meta
dev
Author

TJ Mahr

Published

July 2, 2023

Modified

July 28, 2023

This post is a set of notes on how I migrated my blog from Jekyll (a Ruby based blogging system) to Quarto (a Pandoc/RMarkdown-ish based blogging system).

big ideas first

Over the years on the Jekyll site, blogging from 2016 to 2023, I had slowly opted into customizations, tricks and workarounds. I developed my own build pipeline using targets and knitr. Each post included a _footer.Rmd child document that included the session information at the bottom of the post. I could never get downlit source-code linking to work with Jekyll’s syntax highlighting system, so I recreated the effect where I could. R and Windows could gobble up emoji until very recently so I had used emo::ji() and Github :inline_emoji: syntax as a workaround. I sometimes used Jekyll-specific syntax and site-specific theming—a no-go in Quarto-land—and I have to port them over now too. All of these features and customizations become potential liabilities at migration time.

why make the switch

getting started

Quarto blogs are a supported project template in RStudio, so to create the blog I went to File > New Project > New Directory > Quarto Blog to get the project infrastructure in place. To preview/build the site, I could click Build > Render Website or Ctrl + Shift + B. Easy peasy.

file locations

My Jekyll blog had the following, very simplified, high-level structure.

.
├── 📁 _R 
│   ├── 📁 _drafts
│   ├── 📄 2022-03-10-self-titled-ggplot2-plots.Rmd
│   └── [other .Rmd files]
├── 📁 _posts
│   ├── 📄 2022-03-10-self-titled-ggplot2-plots.md
│   └── [other .md files]
├── 📁 _drafts
├── 📁 _site
│   ├── 📁 self-titled-ggplot2-plots
│   │   └── 📄 index.html
│   ├── [other directories for each post]
|   └── 📄 index.html
└── 📁 assets
|   ├── 📁 css
|   ├── 📁 images
|   |   ├── 🌅 2022-03-neon.jpg
|   |   └── [other images for each post]
|   └── 📁 js
└── 📄 _targets.R

Where the workflow is:

  • I write the post as .Rmd file in the _R folder.
  • I have targets (_targets.R) check the .Rmd files for changes and run knitr on any that have changed, creating the .md file in the _posts folder. (targets also spell-checks all of the files on each run.)
  • I push the .md files to GitHub, and it runs Jekyll to produce the folders and .html files in the _site folder and serve the _site folder to visitors.

If I want to include any non-figure images, I have to store them in assets/images. If want to develop a draft, I have to keep it in a separate workflow. (If an .md file ends up in _posts, it is published.) Finally, the date in the filename is significant. It sets the “date published” for the post, and the date is later stripped off for the final address of the post in the published site.

Here is the parallel set up in my Quarto site:

.
├── 📁 posts 
│   ├── 📁 2022-03-10-self-titled-ggplot2-plots
│   |   ├── 🌅 2022-03-neon.jpg
│   |   └── 📄 index.qmd
│   ├── [other directories for each post]
│   └── 📄 _metadata.yml
├── 📁 docs
│   ├── 📁 posts
|   │   ├── 📁 2022-03-10-self-titled-ggplot2-plots
|   │   |   ├── 🌅 2022-03-neon.jpg
|   │   |   └── 📄 index.html
|   |   └── [other directories for each post]
│   └── 📄 index.html
└── 📄 _quarto.yml

Here, Quarto runs the .qmd -> .md -> .html pipeline, and the docs folder is published as-is as the website on GitHub.

Look at how much simpler this is layout is. Notably:

  • The _drafts folder business is gone, because we use document metadata data (set draft: true in the YAML header) to indicate draft status.
  • Assets for individual posts are stored alongside the post in the post directory.

Here, the date in the filename is not significant to Quarto; it helps me out because I want my posts to be sorted in chronological order by date published. In order to set the date for a post, I have to set date: "2023-07-03" in the document’s YAML header.

broken URLs and redirects

The addresses created by Jekyll did not include the date: self-titled-ggplot2-plots/index.html. But in Quarto, the folder name carries over to the site: posts/2022-03-10-self-titled-ggplot2-plots/index.html. I am not sure how to get Quarto to automatically change output folder names, and this sucks because any pre-existing links to my posts now point to broken URLS.

Fortunately, document metadata can help us here by letting us create an alias for the post:

Quarto index.qmd front matter (key portion)
aliases:
  - "/self-titled-ggplot2-plots/"

Quarto will create a simple page at /self-titled-ggplot2-plots/index.html that will redirect to the proper URL.

the bare minimum for migrating a post

To migrate a single post, we need to do the following steps:

  1. Download the .Rmd file from _R/[post-date-and-filename].Rmd and save it to posts/[post-date-and-filename]/index.qmd.
  2. Download any assets referenced in the .Rmd file to posts/[post-date-and-filename] and update the locations in the .qmd file accordingly.
  3. Migrate YAML metadata.
  4. Replace Jekyll-specific syntax and theming with Quarto versions.

For things like steps 1–3, I was able to write R functions to handle this migration. For things like 4, I wrote an R function to run a series of checks on a file for potential issues.

Here is an example of the functions running on two posts.

source("import-posts.R")
base_url <- "https://raw.githubusercontent.com/tjmahr/tjmahr.github.io/20e6ab146716425f485ca4f0c81f18aaccebb10f" 
post_dir <- "_demo/posts"

data_post <- import_jekyll_post(
  target_post = "_R/2022-03-10-self-titled-ggplot2-plots.Rmd", 
  base_url = base_url, 
  post_dir = post_dir
)
#> ✔ Post folder created '_demo/posts/2022-03-10-self-titled-ggplot2-plots'
#> ✔ Migrated `2022-03-neon.jpg`
#> ✔ Post file created '_demo/posts/2022-03-10-self-titled-ggplot2-plots/index.qmd'

data_post$lines_current |> 
  check_post()
#> Warning: Manually linked code found:
#> [71] [`element_text()`](`r a_element_text`):
#> [115] [`wrap_elements()`](`r a_wrap_elements`) to c
#> [157] [`parse()`](`r a_parse`) and evaluate
#> [158] [`eval()`](`r a_eval`):
#> [176] First, we are going to use [`rlang::expr()`](`r a_expr`) to
#> [244] make is using [`rlang::enexpr()`](`r a_enexpr`) ins

data_post <- import_jekyll_post(
  target_post = "_R/2023-07-03-bayesian-ordering-constraint.Rmd", 
  base_url = base_url, 
  post_dir = post_dir
)
#> ✔ Post folder created '_demo/posts/2023-07-03-bayesian-ordering-constraint'
#> ✔ Migrated `2023-07-ruins-1280.jpg`
#> ✔ Migrated `2023-07-bayes-sign.jpeg`
#> ✔ Post file created '_demo/posts/2023-07-03-bayesian-ordering-constraint/index.qmd'

data_post$lines_current |> 
  check_post()
#> Warning: Jekyll macro syntax found:
#> [289] {% include figure image_path="20
#> Warning: Manually linked code found:
#> [88] Use brms's monotonic effect [`mo()`][brms-mo] syntax.
#> [200] [`contrast()`](https://rdrr.io/r/stats/c
#> [519] [`matlib::Ginv()`](https://cran.r-proj

The check_post() function runs over a file and looks for the lines of text with the following issues:

  • references to assets/
  • Jekyll syntax
  • manually linked code (which should be handled by downlit)
  • emoji workarounds (use native emoji)

specific migration problems

post footers

At the end of each post on the Jekyll site, I included a footer with

  1. the date the post was knitted,
  2. a link to the post’s source code on GitHub, and
  3. a footnote with the session info.

We can get (1) for free by using inline R code in the YAML front matter on each post:

index.qmd front matter (key portion)
date-modified: "`r format(Sys.Date())`"

We can also get (2) for free by using more YAML features. First, we tell Quarto the URL for the GitHub repo:

_quarto.yml (key portion)
website:
  repo-url: "https://github.com/tjmahr/quarto-blog"

Then enable links to the source code on all the posts. I probably could have included this setting _quarto.yml but I didn’t want to deal with the link being enabled unintentionally if I create any non-blogpost documents.

posts/_metadata.yml (key portion)
code-tools:
  source: repo

As for (3), I had implemented this featured originally in a pretty unsophisticated manner: A file called _R/_footer.Rmd generated the post footer, and I included it as a child document in knitr at the bottom of every post.

```{r, include = FALSE}
.parent_doc <- knitr::current_input()
```
```{r, child = "_R/_footer.Rmd"}
```

We are going to do the same thing, but using Quarto. The bottom of each post now includes an include shortcode:

{{< include ../_footer.Rmd >}}

File-paths in a blog post are relative to the index.qmd file—which is nice for things like datasets or image assets—so to go up to the parent directory for _footer.Rmd, we use ../.

post headers

On the Jekyll site, I could include a banner image, a caption for the banner image, and a color overlay for the image using YAML metadata:

Jekyll Rmd front matter (key portion)
header:
  overlay_image: assets/images/2022-03-neon.jpg
  image_description: A neon sign that says 'neon'
  overlay_filter: rgba(10, 10, 10, 0.5)
  caption: 'Photo credit: [**Slava Kuzminsk**](https://unsplash.com/photos/qnHOFl4VuFc)'

Here is the result:

A header on the Jekyll site. It is a neon sign of the word 'neon'.

The post featured here is about ggplot2 plots that contain their own source code in the title, so the background image of a neon sign of the word “neon” is a thematic gag.

We can get a banner image in Quarto using the YAML front matter:

Quarto index.qmd front matter (key portion)
title-block-banner: 2022-03-neon.jpg
image: 2022-03-neon.jpg

where image set the image that accompanies the post on the list of blog posts.

Here is the result:

A header on the default Quarto site. We cannot see the word 'neon'.

Note that we cannot see the word “neon” and the title is hard to read without the overlaid color on the image. We can fix these issues by using CSS to create a color background that blends with the image and centering the background image. I also set the font-color to be white for added contrast. In Jekyll, we could customize the color layer for each post, but we do not have that ability here. We store this custom css in title-block.css and tell _quarto.yaml about it.

title-block.css
.quarto-title-block .quarto-title-banner {
  background-position-x: 50%;
  background-position-y: 50%;
  background-blend-mode: overlay;
  background-color: rgba(10, 10, 10, .5);
  color: rgb(255, 255, 255);
}
_quarto.yml (key portion)
format:
  html:
    css:
      - title-block.css

We still have a major problem at this point: We cannot use these banner images without including the photo credit credit. Fortunately, we can patch this title banner by using a template partial. Quarto documents like these blog posts are built by plugging data into an HTML template, and this template is itself templated (these subtemplates are the partials). That means, we can change just the title banner template and keep the other parts in tact.

First, let’s make the photo caption into a structured piece of metadata in the YAML front matter. This step will create pieces of data the template can see:

index.qmd front matter (key portion)
title-block-banner-caption: 
  credit: Slava Kuzminsk
  link: https://unsplash.com/photos/qnHOFl4VuFc

We download the HTML title banner template from GitHub and store it in partials/title-block.html. We then modify the file, adding the unindented lines towards the bottom.

partials/title-block.html
<header id="title-block-header" class="quarto-title-block default$if(quarto-template-params.banner-header-class)$ $quarto-template-params.banner-header-class$$endif$">
  <div class="quarto-title-banner">
    <div class="quarto-title column-body">
      <h1 class="title">$title$</h1>
      $if(subtitle)$
      <p class="subtitle lead">$subtitle$</p>
      $endif$
      $if(description)$
      <div>
        <div class="description">
          $description$
        </div>
      </div>
      $endif$
      $if(categories)$
        $if(quarto-template-params.title-block-categories)$
      <div class="quarto-categories">
        $for(categories)$
        <div class="quarto-category">$categories$</div>
        $endfor$
      </div>
        $endif$
      $endif$
    </div>

<!-- 🆕 start -->
$if(title-block-banner-caption)$
<div class = "title-block-caption">
Photo credit: <a href="$title-block-banner-caption.link$">$title-block-banner-caption.credit$</a>
</div>
$endif$
<!-- 🆕 end -->
  
  </div>
  $title-metadata.html()$
</header>

In Pandoc’s templating language, we can refer to YAML front matter variables by using dollar signs. For example, $title$ inserts the title, and $title-block-banner-caption.link$ inserts the link property of the title-block-banner-caption property. In the added lines, we check for a title-block-banner-caption and then add a <div> with a link to the photo source.

To activate this partial template, we have to add a field to _quarto.yml:

_quarto.yml (key portion)
format:
  html:
    template-partials:
      - partials/title-block.html

We need this addition to the title block to look okay, so we amend title-block.css:

title-block.css
.quarto-title-block .quarto-title-banner {
  background-position-x: 50%;
  background-position-y: 50%;
  background-blend-mode: overlay;
  background-color: rgba(10, 10, 10, .5);
  color: rgb(255, 255, 255);
}

.quarto-title-block .quarto-title-banner .title-block-caption {
  font-size: .75em;
  color: rgba(255,255,255,.7);
}

.quarto-title-block .quarto-title-banner .title-block-caption a {
  text-decoration: underline !important;
  color: rgba(255,255,255,.7);
}

At last, we have a decent looking title banner with a background image and a photo credit.

A header on the default Quarto site. We can see the word 'neon' and a link to the source of the photo.

other notes

homepage / listings

tktktk

scratch

  • I want a banner image on top like the old site with a credit to the source.

  • I had to put fig-width: 8 under format > html in _quarto.yml. I also pushed other knitr options here:

    fig-asp: 0.618
    fig-dpi: 300
    fig-align: "center"
    fig-cap-location: "margin"
  • I am not sure how to replicate the out.width knitr option.
  • I want to use targets instead of freeze.
  • I need old URLs to redirect. Use aliases: [""] in header.

I got to opt into fig-cap-location: "margin".

I had to remove the old knitr _cached stuff. Still need a different solution.

I don’t want the green links. If I set the link color, that still leaves the green link color in the table of contents.

  • I want a slightly larger font size. Set fontsize: 1.1em in _quarto.yml

  • All of my old manual downlit attempts are styled differently than actual downlit urls. Added a check.

  • I want underlined downlit links. Done by adding a new css file.

I have to convert fig.alt and fig.cap to here.

#| fig-alt: "Examples of `plot_dist()`"
#| fig-cap:

This is the not first post in a Quarto blog. Welcome! It’s configuration. Lots of configuration.

  • project level
  • directory level
  • post-level yaml

Session info
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.3.0 (2023-04-21 ucrt)
 os       Windows 11 x64 (build 22621)
 system   x86_64, mingw32
 ui       RTerm
 language (EN)
 collate  English_United States.utf8
 ctype    English_United States.utf8
 tz       America/Chicago
 date     2023-07-28
 pandoc   3.1.1 @ C:/Program Files/RStudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)
 quarto   1.3.353

─ Packages ───────────────────────────────────────────────────────────────────
 package     * version date (UTC) lib source
 brio          1.1.3   2021-11-30 [1] CRAN (R 4.3.0)
 cli           3.6.1   2023-03-23 [1] CRAN (R 4.3.0)
 curl          5.0.1   2023-06-07 [1] CRAN (R 4.3.0)
 digest        0.6.33  2023-07-07 [1] CRAN (R 4.3.1)
 evaluate      0.21    2023-05-05 [1] CRAN (R 4.3.0)
 fansi         1.0.4   2023-01-22 [1] CRAN (R 4.3.0)
 fastmap       1.1.1   2023-02-24 [1] CRAN (R 4.3.0)
 fs            1.6.3   2023-07-20 [1] CRAN (R 4.3.1)
 glue          1.6.2   2022-02-24 [1] CRAN (R 4.3.0)
 htmltools     0.5.5   2023-03-23 [1] CRAN (R 4.3.0)
 htmlwidgets   1.6.2   2023-03-17 [1] CRAN (R 4.3.0)
 jsonlite      1.8.7   2023-06-29 [1] CRAN (R 4.3.1)
 knitr         1.43    2023-05-25 [1] CRAN (R 4.3.0)
 lifecycle     1.0.3   2022-10-07 [1] CRAN (R 4.3.0)
 magrittr      2.0.3   2022-03-30 [1] CRAN (R 4.3.0)
 pillar        1.9.0   2023-03-22 [1] CRAN (R 4.3.0)
 pkgconfig     2.0.3   2019-09-22 [1] CRAN (R 4.3.0)
 ragg          1.2.5   2023-01-12 [1] CRAN (R 4.3.0)
 rlang         1.1.1   2023-04-28 [1] CRAN (R 4.3.0)
 rmarkdown     2.23    2023-07-01 [1] CRAN (R 4.3.0)
 rstudioapi    0.15.0  2023-07-07 [1] CRAN (R 4.3.1)
 sessioninfo   1.2.2   2021-12-06 [1] CRAN (R 4.3.0)
 stringi       1.7.12  2023-01-11 [1] CRAN (R 4.3.0)
 stringr       1.5.0   2022-12-02 [1] CRAN (R 4.3.0)
 systemfonts   1.0.4   2022-02-11 [1] CRAN (R 4.3.0)
 textshaping   0.3.6   2021-10-13 [1] CRAN (R 4.3.0)
 tibble        3.2.1   2023-03-20 [1] CRAN (R 4.3.0)
 utf8          1.2.3   2023-01-31 [1] CRAN (R 4.3.0)
 vctrs         0.6.3   2023-06-14 [1] CRAN (R 4.3.1)
 xfun          0.39    2023-04-20 [1] CRAN (R 4.3.0)
 yaml          2.3.7   2023-01-23 [1] CRAN (R 4.3.0)

 [1] C:/Users/Tristan/AppData/Local/R/win-library/4.3
 [2] C:/Program Files/R/R-4.3.0/library

──────────────────────────────────────────────────────────────────────────────