Problem with custom shortcode for citations

I have written a shortcode for citations, heavily inspired by the work of Nelis Oostens, which works the way I wanted. In fact it goes slightly further in the sense that it automatically formats the source of the quotation using MLA citation format. (An example of the shortcode in use can be seen on the test article on my staging site.)

1. Shortcode [edited]

{{ printf "<figure class=\"quotation\">" | htmlUnescape | safeHTML }}
{{- $split := split .Inner "<-source->" -}}
{{ printf "<blockquote>" | htmlUnescape | safeHTML }}
{{ printf "<p>" | htmlUnescape | safeHTML }}
{{ index $split 0 | safeHTML }}
{{ printf "</p>" | htmlUnescape | safeHTML }}
{{ printf "</blockquote>" | htmlUnescape | safeHTML }}
{{ printf "<figcaption class=\"source\">" | htmlUnescape | safeHTML }}
  {{ index $split 1 | safeHTML }}{{ if .Get "main-title" }}. {{ if .Get "secondary-title" }}{{ printf "<q>" | htmlUnescape | safeHTML }}{{ .Get "secondary-title" | htmlUnescape | safeHTML  }}{{ printf ".</q> " | htmlUnescape | safeHTML }}{{ end }}{{ printf "<cite>" | htmlUnescape | safeHTML }}{{ if .Get "url" }}[{{ .Get "main-title" }}]({{ .Get "url" }}){{ else }}{{ .Get "main-title" }}{{ end }}{{ printf "</cite>" | htmlUnescape | safeHTML }}{{ end }}{{ if .Get "publisher" }}. {{ .Get "publisher" | htmlUnescape | safeHTML  }}{{ end }}{{ if .Get "date" }}, {{ .Get "date" | htmlUnescape | safeHTML }}{{ end }}{{ if .Get "pages" }} ({{ .Get "pages" | htmlUnescape | safeHTML }}){{ end }}.
  {{ printf "</figcaption>" | htmlUnescape | safeHTML }}
{{ printf "</figure>" | htmlUnescape | safeHTML }}

2. References in post content

{{% quotation main-title="Moralia" url="<https://en.wikipedia.org/wiki/Moralia>](https://en.wikipedia.org/wiki/Moralia)" secondary-title="On Moral Virtue" date="c. 100 AD" publisher="Loeb" page="71" %}}
But for the sake of some little mouthful of flesh we deprive a soul of the sun and light, and of that proportion of life and time it had been born into the world to enjoy.
<-source->
Plutarch[^1]
{{% /quotation %}}

[^1]: Cited in Franklin, Julian H., {{% cite %}}Animal rights and moral philosophy{{% /cite %}}. New York: Columbia University Press, 2005, p.&nbsp;6.

3. SCSS

.source {
  padding: 0 0 5%;
  margin: 0;
  text-align: right;
}

What I haven’t been able to do are two things:

1. Add an emdash (—) at the beginning of the <figcaption> element

In my previous WordPress site, I used the .source class to add this automatically, like this:

.source {
  padding: 1% 0 5%;
  text-align: right;
  
  &::before {
    content: '\2014\2009';
  }
  
  &::after {
    content: '.'
  }
  
}

Unfortunately in Hugo the pseudo-elements in the .source class break the code and prevent the Markdown from rendering as html. Any suggestions as to how this can be rendered would be welcome.

2. Render the parameter for page numbers differently when a range of pages is entered

At present, using MLA conventions, I’m wrapping the parameter for page numbers in parentheses. In practice though in the context of a blog, I don’t think this is ideal and would prefer to display the pages parameter differently depending on whether a single page (‘p. 71’), a continuous range of pages (‘pp 71-75’) or a discontinuous list of pages (‘pp 71, 76, 80’). Is there any way this can be done?

Perhaps I don’t understand what you’re saying, but that doesn’t seem to make sense to me. Hugo has nothing to do with pseudo-selectors like ::before, simply because it doesn’t know about them. I suggest having a look at the generated HTML in your browser’s developer tools and figuring out what goes wrong there.

Unrelated: I don’t understand either why you’re using all these printf statements instead of something like

{{$split := split .Inner "<-source->"}}
<figure class="quotation">
<blockquote>
<p>
{{index $split 0 | safeHTML}}
</p></blockquote>
<figcaption class="source">
{{index $split 1 | safeHTML}}
...
</figcaption>
</figure>

and something like {{with .Get "date"}}, {{.}}{{end}} instead of the quite convoluted if… thingies.

Currently, your shortcode does not seem to do anything with the page parameter. You could do something like

{{$pages:= ""}}
{{with .Get "page"}}
{{if strings.ContainsAny . "-,"}}
  {{$pages = printf "pp %s" .}}
{{else}}
  {{$pages = printf "p %s" .}}
{{end}}
{{end}}

The with assigns the value of page to the dot, and strings.ContainsAny is true if this value contains either a comma or a dash. So, if you use page="71", you’ll get p 71, and for page=71-74, you’ll get pp 71-74 etc. If you don’t pass a page parameter to your shortcut, $pages will be the empty string which prints just fine.

Thanks very much. Your point completely taken (and implemented regarding the top and bottom bits, will do so at my leisure for simplifying the convoluted inner component) regarding the printf statements. :stuck_out_tongue_closed_eyes:

The shortcode (still work in progress for the part inside the <figcaption></figcaption> element) now looks as follows:

{{$split := split .Inner "<-source->"}}
<figure class="quotation">
  <blockquote>
    {{ index $split 0 | safeHTML }}
  </blockquote>
  <figcaption class="source">
    {{ index $split 1 | safeHTML }}{{ if .Get "main-title" }}. {{ if .Get "secondary-title" }}{{ printf "<q>" | htmlUnescape | safeHTML }}{{ .Get "secondary-title" | htmlUnescape | safeHTML  }}{{ printf ".</q> " | htmlUnescape | safeHTML }}{{ end }}{{ printf "<cite>" | htmlUnescape | safeHTML }}{{ if .Get "url" }}[{{ .Get "main-title" }}]({{ .Get "url" }}){{ else }}{{ .Get "main-title" }}{{ end }}{{ printf "</cite>" | htmlUnescape | safeHTML }}{{ end }}{{ if .Get "publisher" }}. {{ .Get "publisher" | htmlUnescape | safeHTML  }}{{ end }}{{ if .Get "date" }}, {{ .Get "date" | htmlUnescape | safeHTML }}{{ end }}{{ if .Get "pages" }} ({{ .Get "pages" | htmlUnescape | safeHTML }}){{ end }}
  </figcaption>
</figure>

1. Regarding the reason why the em-dash didn’t display correctly, you’re completely right and I should have checked the source code before posting. This showed that {{index $split 0 | safeHTML}} and {{index $split 1 | safeHTML}} had both been wrapped, after deployment, in additional <p> selectors that I hadn’t included in the original shortcode. As a result .source was targeting the first of the two <p> selectors, breaking the layout. So I removed the <p> I had included inside the <figcaption></figcaption> and changed the SCSS to the following:

.source {
  padding: 0 0 5%;
  margin: 0;
  text-align: right;
}

.source p::before {
  content: "\2014\2009";
}

.source p::after {
  content: ".";
}

This causes the html to display the em-dash in front of the caption, solving my first problem which wasn’t directly (but only indirectly) Hugo-related. Thanks very much!

2. The code you suggested for displaying different renderings of the pages, however, produces an empty result. I tried it as follows (adding the missing ‘s’ inside {{with .Get "pages"}}:

  {{$pages:= ""}}
  {{with .Get "pages"}}
  {{if strings.ContainsAny . "-,"}}
    {{$pages = printf "pp %s" .}}
  {{else}}
    {{$pages = printf "p %s" .}}
  {{end}}
  {{end}}

Your sample call used page, though. Just be consistent, than it should work. And I’d use this code for the rest of your shortcode:

{{with .Get "main-title" }}
  {{$main_title := .}}
  .
  {{ with .Get "secondary-title" }}
    <q>{{.}}</q>
  {{ end }}
  <cite>
  {{ with .Get "url" }}
    [$main_title]({{.}})
  {{ else }}
    $main_title
  {{ end }}
  </cite>
{{ end }}
{{ with .Get "publisher" }}{{.}}{{ end }}
{{ with .Get "date" }}, {{.}}{{ end }}
{{ with .Get "pages" }}({{.}}){{ end }}

That’s, in my opinion, a lot easier to read, understand, and modify than your original version.

1. Thanks! I agree my code is a little convoluted. The citation shortcode now works and displays Markdown notes in the final html—which I previously hadn’t been able to get to work when using markdownify on the .Inner content.

2. I’ve now gone ahead and extended the same principle to my video shortcode, which is quite a bit more complicated because the html is deployed slightly differently depending on the choices made for the thumbnail.

2.1. The parameters are set in config.toml, like so (I use Cloudflare Stream to host my videos, and Cloudflare R2 with a custom domain to host the thumbnails):

[params]
  ...
  cloudflare_images = "https://[CLIENT_URL}/cdn-cgi/imagedelivery[IMAGE_ID]/"
  cloudflare_assets = "https.assets.via.dj/"
  video_url = "https://customer-[CLIENT_ID].cloudflarestream.com/"
  video_suffix = "/manifest/video.m3u8"
  thumbnail_suffix = "/thumbnails/thumbnail.jpg?time="
  asset_thumbnails = "https://assets.via.dj/images/thumbnails/"

2.2. I then call them in the shortcode:

{{ $data := newScratch }}
{{ $path := site.Params.video_url }}
{{ $data.Set "url" $path }}
{{ $src := .Get "src" }}
{{ $data.Add "url" $src }}

{{ $video_url := $data.Get "url" }}
{{ $data.Set "video-url" $video_url }}
{{ $video_suffix := site.Params.video_suffix }}
{{ $data.Add "video-url" $video_suffix }}
{{ $video_url = $data.Get "video-url" }}

{{ $thumbnail_url := $data.Get "url" }}
{{ $data.Set "thumbnail-url" $thumbnail_url }}
{{ $thumbnail_suffix := site.Params.thumbnail_suffix }}
{{ $data.Add "thumbnail-url" $thumbnail_suffix }}
{{ $thumb_time := "1" }}

{{ if .Get "thumb-time" }}
{{ $thumb_time = .Get "thumb-time" }}
{{ end }}

{{ $data.Add "thumbnail-url" $thumb_time }}
{{ $data.Add "thumbnail-url" "s" }}
{{ $thumbnail_url := $data.Get "thumbnail-url" }}

{{ if .Get "thumbnail" }}
{{ $thumbnail_url = site.Params.asset_thumbnails }}
{{ $data.Set "thumbnail-url" $thumbnail_url }}
{{ $data.Add "thumbnail-url" $src }}
{{ $data.Add "thumbnail-url" ".jpg" }}
{{ $thumbnail_url = $data.Get "thumbnail-url" }}
{{ end }}

{{ $aspect_ratio := "16:9" }}
{{ if .Get "aspect-ratio" }}
{{ $aspect_ratio = .Get "aspect-ratio" }}
{{ end }}

{{ printf "<figure class=\"player\">" | htmlUnescape | safeHTML }}
{{ printf "<media-player" | htmlUnescape | safeHTML }}
  {{ printf "src=\" %s" $video_url | htmlUnescape | safeHTML }}"
  {{ printf "poster=\" %s" $thumbnail_url | htmlUnescape | safeHTML }}"
  {{ printf "controls" | htmlUnescape | safeHTML }}
  {{ printf "aspect-ratio=\" %s" $aspect_ratio | htmlUnescape | safeHTML }}"
  {{ printf "load=\"visible\"" | htmlUnescape | safeHTML }}
  role="region"
{{ printf ">" | htmlUnescape | safeHTML }}
  {{ printf "<media-outlet>" | htmlUnescape | safeHTML }}{{ printf "</media-outlet>" | htmlUnescape | safeHTML }}
{{ printf "</media-player>" | htmlUnescape | safeHTML }}
{{ if .Inner }}
{{ printf "<figcaption>" | htmlUnescape | safeHTML }}
{{ .Inner | safeHTML }}
{{ printf "</figcaption>" | htmlUnescape | safeHTML }}
{{ end }}
{{ printf "</figure>" | htmlUnescape | safeHTML }}

This code works, and allows me to include Markdown notes in my video captions, but I’d like to make it a bit simpler by concatening variables, rather than adding them one to another using .Scratch. Except I haven’t been able to find out how to do this in the docs.