Making a Picture of the Week feature on Hugo (Updated)
Updated: See the last section
I wanted to take advantage of the flexibility I gave myself with Hugo to have a “Picture of the Week” (PotW) feature on my new site. It took a few iterations to get it to where I liked it, and there are some things about Hugo I learned along the way, but it’s done enough for now.
The basic idea is that I want to take advantage of the streamlined upload and metadata workflow I’ve set up between Lightroom and imgup to share photos without a lot of repeating myself when it comes to writing titles, alt text, etc.
First try
My first iteration was to keep the data for a featured picture in the site config, like this:
1params:
2 potw_img_url: https://photos.smugmug.com/photos/i-TkDZjHx/0/ce3d02d6/XL/i-TkDZjHx-XL.jpg
3 potw_caption: Manzanita Beach at Dawn
4 potw_alt: A rocky beach at dawn with hills and a mountain shrouded in mist. A person in a red jacket looks over the ocean.
5 potw_gallery_link: https://pix.puddingtime.org/Uploads/n-47GBfb/i-TkDZjHx
… then pull use a partial:
1<div class='front-image'>
2<figure>
3 <a href="{{ .Site.Params.potw_gallery_link }}" alt="{{ .Site.Params.potw_alt }}">
4 <img src="{{ .Site.Params.potw_img_url }}" />
5 </a>
6<figcaption>
7Picture of the Week: {{ .Site.Params.potw_caption }}
8</figcaption>
9</figure>
10</div>
That worked fine for my purposes, but I didn’t like all the clicking to get SmugMug photo info into the config, and I hated the idea of touching my config to do a simple task.
It also had no memory of previous images and I preferred the idea of building a list of these items over time to make a specific PotW page, or to just have them in the record.
What I’ve settled on now is a little less clicky, and I’ve got an idea for how to make it even less clicky.
Second try
I started by setting up a specific PotW album in SmugMug. Anyone with the URL can see it, but it’s not exposed right now. I upload photos to there.
Next, I made a shortcode that can talk to metadata stored in a page:
1<figure>
2 <a href="{{ $.Page.Params.potw_gallery_link }}" alt="{{ $.Page.Params.potw_alt }}">
3 <img src="{{ $.Page.Params.potw_img_url }}" />
4 </a>
5<figcaption>
6{{ $.Page.Params.title }}
7</figcaption>
8</figure>
As you can see, the frontmatter needs to have a few extra bits in it:
1---
2title: 'Picture of the Week: Profit from the Panic'
3potw_img_url: https://photos.smugmug.com/photos/i-87Bm3V2/0/ecba06f4/XL/i-87Bm3V2-XL.jpg
4potw_alt: Wheatpaste of a tv mounted on a human body giving a thumbs up. The TV reads "Profit from the Panic"
5potw_gallery_link: https://pix.puddingtime.org/Picture-of-the-Week/n-D5HJ4W/i-87Bm3V2
6
7date: 2023-02-01T20:10:15-0800
8tags: ['potw','photography']
9draft: false
10---
imgup’s PotW page gives me the YAML frontmatter:
I can copy and paste it into a PotW post along with the shortcode and it’s ready to go.
Pulling it into the front page is just a partial that pulls in the most recent item tagged potw
:
1{{ range first 1 .Site.Taxonomies.tags.potw }}
2
3<div class='front-image'>
4<h3>Picture of the Week</h3>
5
6<figure>
7 <a href="{{ .Page.Params.potw_gallery_link }}" alt="{{ .Page.Params.potw_alt }}">
8 <img src="{{ .Page.Params.potw_img_url }}" />
9 </a>
10<figcaption>
11{{ .Page.Params.title }}
12</figcaption>
13</figure>
14</div>
15{{ end }}
What’s next
It was quick and easy to just copy a route in imgup to make the PotW page, but it’s still manual and clicky to make a PotW post. So my next step will be to hang an endpoint off imgup that automates the process of getting the most recent PotW gallery image and its metadata so I don’t have to write the posts at all.
Update
Sinatra made it easy to add a tiny bit of logic to the existing PotW route to make it send back JSON:
1unless json == true
2 haml :potw
3else
4 content_type :json
5 @recents.first.to_json
6end
A simple script hits the endpoint, grabs the JSON, and writes it out to a file:
1##!/usr/bin/env ruby
2
3require 'net/http'
4require 'uri'
5require 'json'
6require 'date'
7require 'slugify'
8require 'yaml'
9require 'optparse'
10
11# set default tags for each entry. The "tags" option allows you to add more
12tags = ['photography', 'potw']
13
14options = {}
15
16OptionParser.new do |parser|
17 parser.on("-t", "--test", "Changes the endpoint to localhost:4567") do |o|
18 options[:test] = true
19 end
20
21 parser.on("-o", "--overwrite", "Danger: Overwrite the existing potw if there's a conflict.") do |o|
22 options[:overwrite] = true
23 end
24
25 parser.on("-T", "--tags TAGS", "Comma-delimited tags for the post, e.g. 'banana,apple,pear'") do |o|
26 options[:tags] = o.split(',')
27 options[:tags].each do |t|
28 tags << t
29 end
30 end
31
32 parser.on("-h", "--help", "Get help.") do |o|
33 puts parser
34 exit(0)
35 end
36
37end.parse!
38
39if options[:test] == true
40 endpoint = 'http://localhost:4567/potw?json=1'
41else
42 endpoint = 'https://imgup.puddingtime.org/potw?json=1'
43end
44
45site_posts_dir = "~/src/simple/content/posts/"
46
47date = Date.today.strftime("%Y-%m-%d")
48long_date = Time.now.strftime("%Y-%m-%dT%H:%M:%S%z")
49title = date + '-potw'
50
51slug = title.slugify
52filename = slug + '.md'
53file_path = File.expand_path(site_posts_dir + filename)
54
55if File.exists?(file_path) && options[:overwrite] != true
56 abort("*** Error: #{file_path} already exists. Exiting.")
57end
58
59
60uri = URI.parse(endpoint)
61request = Net::HTTP::Get.new(uri)
62request.basic_auth(ENV['IMGUP_USER'], ENV['IMGUP_PASS'])
63
64req_options = {
65 use_ssl: uri.scheme == "https",
66}
67
68response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
69 http.request(request)
70end
71
72json = JSON.parse(response.body)
73yaml = YAML.dump(json)
74
75potw_post = <<-POTW
76#{yaml}
77
78date: #{long_date}
79tags: #{tags}
80categories: ['photography']
81draft: true
82---
83
84# escaped to keep hugo from parsing this as a shortcode:
85# {\{< potw >}}
86
87POTW
88
89File.write(file_path, potw_post)
90
91`open #{file_path}`
Much more turnkey than it was, and with a little more logic I might be able to build it into a pre-build script of some kind to completely automate the whole thing.