I’ve recently started using pblog, a tool Bradley Taunt created to build blogs from text files using pandoc. It’s a handy little tool, but I couldn’t resist making a few customizations and submitting them. To my surprise, Bradley accepted most of them.
Nor could I resist making further tweaks after updating my copy to account for his changes, but I probably won’t submit these as patches. I’ll just point him to this article. 😸
Tweaks Rejected for Good Reason
The only change he partially rejected were my adjustments to the
makefile that would convert JPEG and PNG images to more
modern formats (WEBP and AVIF) for use in blog posts and pages using HTML5’s
<picture> element. It would have added more
complexity than he wanted to include by default, but still documented it
as an optional
tweak.
Which is perfectly fair. Here’s a diff between default
makefile for pblog and mine:
diff --git a/../pblog.xyz/makefile b/makefile
index f8b1acb..882881c 100644
--- a/../pblog.xyz/makefile
+++ b/makefile
@@ -1,12 +1,51 @@
+.SUFFIXES: .png .jpg .webp .avif
+
+.jpg.webp:
+ magick -quality 80 "$<" "$@"
+
+.jpg.avif:
+ magick -quality 80 "$<" "$@"
+
+.png.webp:
+ magick -quality 80 "$<" "$@"
+
+.png.avif:
+ magick -quality 80 "$<" "$@"
+
+JPEGS!=find media/ -name '*.jpg'
+PNGS!=find media/ -name '*.png'
+
+JPEG_WEBP=${JPEGS:.jpg=.webp}
+JPEG_AVIF=${JPEGS:.jpg=.avif}
+
+PNG_WEBP=${PNGS:.png=.webp}
+PNG_AVIF=${PNGS:.png=.avif}
+
.DEFAULT: build
+include ./sshvars
+
+.PHONY: archives
+archives:
+ zip -r -j -FS media/posts.zip posts/*.md
+ zip -r -j -FS media/starbreaker.zip starbreaker/*
+ zip -r -j -FS media/limyaael.zip limyaael/*
.PHONY: build
-build:
- bash pblog.sh > _output/feed.xml
+build: archives $(JPEG_WEBP) $(JPEG_AVIF) $(PNG_WEBP) $(PNG_AVIF)
+ ./pblog.sh > _output/feed.xml
xsltproc _output/feed.xml | tail -n +2 > _output/blog/index.html
+ rsync -av favicons/ _output/
serve: build
python3 -m http.server --directory _output/
+install: build
+ rsync --rsh="ssh ${SSH_OPTS}" \
+ --delete-delay \
+ --exclude-from='./rsync-exclude.txt' \
+ -acvz _output/ ${SSH_USER}@${SSH_HOST}:${SSH_PATH}
+
+.PHONY: clean
clean:
- rm _output/* rss/*
+ @rm -rf _output/* rss/* posts/*.html pages/*.html posts/.DS_Store pages/.DS_Store $(JPG_WEBP) $(JPEG_AVIF) $(PNG_WEBP) $(PNG_AVIF)
+Automatic conversion of PNG and JPEG images is of use to me because I want to use more modern and efficient file formats when sharing images so that I don’t eat up visitors’ bandwidth, but that isn’t a priority for everybody.
Furthermore, I’ve got a target for zipping up posts for downloads, as well as archives for my fiction and a set of rants about writing sf and fantasy originally posted on LiveJournal. Zipping up posts might be of use to others, but that doesn’t need to be part of the default makefile.
Likewise my use of rsync to transfer my site to my host. I might be
comfortable with rsync and ssh, with the key
management that entails, but others might prefer a different approach.
They might want a file transfer tool with a graphical interface like Forklift or Transmit. Or they
might be hosting on Netlify or in a S3 bucket.
And these are just changes to the makefile. I’ve further
altered other parts of pblog, including the central script. I’ll explore
these shortly.
Tweaking the Stylesheet
The stylesheet for my site is also rather different from the default. See for yourself.
diff --git a/../pblog.xyz/style.css b/style.css
index b969c19..27006a5 100644
--- a/../pblog.xyz/style.css
+++ b/style.css
@@ -1,6 +1,66 @@
+html {
+ font-size: 16px;
+}
+
+@media only screen and (max-width: 1366px) {
+ html {
+ font-size: 20px;
+ }
+}
+
+@media only screen and (max-width: 1920px) {
+ html {
+ font-size: 24px;
+ }
+}
+
+@media only screen and (max-width: 2560px) {
+ html {
+ font-size: 28px;
+ }
+}
+
+@media only screen and (max-width: 3840px) {
+ html {
+ font-size: 32px;
+ }
+}
+
body {
- max-width: 75ch;
- line-height: 1.4;
+ margin: 0 auto;
+ max-width: 66ch;
+ color: #16161D;
+ background: #FAFAFA;
+ padding: 0 .62rem;
+ font: 1rem/1.62 -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
+}
+
+h1, h2, h3, dt, dd {
+ line-height: 1.2;
+}
+
+a:link {
+ color: #0F4880;
+}
+
+a:hover {
+ color: #800080;
+}
+
+a:visited:hover {
+ color: #205E3B;
+}
+
+a:visited {
+ color: #800000;
+}
+
+hr {
+ border: 1px solid #16161D;
+}
+
+dt {
+ font-weight: bold;
}
p code, li code {
@@ -11,29 +71,62 @@ p code, li code {
}
img {
- height: auto;
+ height: auto;
max-width: 100%;
}
-pre {
- background: #f9f9f9;
- border: 1px solid lightgrey;
- padding: 5px;
+blockquote {
+ font-family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
+ border-left: 2px solid #16161d;
+ padding-left: 1ch;
+ margin-left: 4ch;
+}
+
+.date {
+ color: grey;
}
#TOC {
- border: 1px solid;
- position: relative;
+ border: 1px solid #16161d;
+ position: relative;
}
#TOC:before {
- border-bottom: 1px solid;
- content: 'Table of Contents';
- display: block;
- font-weight: bold;
- padding: 5px;
- position: relative;
+ border-bottom: 1px solid #16161d;
+ content: 'Table of Contents';
+ display: block;
+ font-weight: bold;
+ padding: 5px;
+ position: relative;
}
-.date {
- color: grey;
+#skip a
+{
+ position:absolute;
+ left:-10000px;
+ top:auto;
+ width:1px;
+ height:1px;
+ overflow:hidden;
+}
+
+#skip a:focus
+{
+ position:static;
+ width:auto;
+ height:auto;
+}
+
+@media print {
+ body {
+ font-size: 12px;
+ line-height: 1.5;
+ font-family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
+ color: black;
+ background: white;
+ }
+
+ a {
+ color: black;
+ text-decoration: underline;
+ }
}I’ve made the following changes:
- I’ve set a base font size, which I adjust based on screen width in a crude attempt at responsive text sizes.
- I’ve narrowed the maximum body width from 75ch units to 66ch.
- I’ve changed the text color from black to intrinsic gray and the background color to off-white.
- Border colors are the same color as the text color.
- I’ve set the font size to 1rem, so that it adjusts responsively, set
a reasonable line height, and used a system font stack instead of just defalting to
sans-serif. - I’ve set link colors to values with sufficient contrast to satisfy WCAG 2.0 Level AAA requirements picked from http://colorsafe.co
- definition list titles are bold
<hr>element is now styled<blockquote>has a border to the left and is rendered with a serif font stack.- I’ve included a media query for print with what I hope are reasonable settings. (They look OK to me.)
Many of these changes aren’t strictly necessary; they exist to suit my tastes and requirements – which are hardly universal.
Tweaking the XSL Transform
I’ve also modified the XSL transform used to generate
blog/index.html from feed.xml. Some of this
was to suit my preferences, but one change proved necessary for
functionality’s sake. See for yourself.
diff --git a/../pblog.xyz/rss.xsl b/rss.xsl
index e4e88c3..ec542c5 100644
--- a/../pblog.xyz/rss.xsl
+++ b/rss.xsl
@@ -18,58 +18,96 @@
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no" />
<link rel="stylesheet" href="../style.css" />
<style>
- /* THIS STYLING HIDES THE EXTRA "DOCTYPE" PLAIN TEXT */
- html {
- background: white;
- height: 100%;
- overflow: auto;
- position: fixed;
- top: 0;
- width: 100%;
- }
- header {
- border-bottom: 1px solid lightgrey;
- margin-bottom: 0;
- padding-bottom: 0.5em;
- }
- header h1 {
- margin: 0;
- }
- header p {
- margin: 0 0 0.5em;
- }
- .date {
- display: block;
- font-family: monospace;
- margin-top: 1em;
- overflow: hidden;
- white-space: nowrap;
- width: 16ch;
- }
+ /* THIS STYLING HIDES THE EXTRA "DOCTYPE" PLAIN TEXT */
+ header {
+ border-bottom: 1px solid #16161d;
+ margin-bottom: 1rem;
+ padding-bottom: 0.5em;
+ }
+
+ header h1 {
+ margin: 0;
+ }
+
+ header p {
+ margin: 0 0 0.5em;
+ }
+
+ .date {
+ display: block;
+ font-family: monospace;
+ margin-top: 1em;
+ overflow: hidden;
+ white-space: nowrap;
+ width: 16ch;
+ }
</style>
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
+ <link rel="manifest" href="/site.webmanifest" />
+ <link rel="me" href="https://twitter.com/MGraybosch" />
+ <meta property="og:title" content="402 PAYMENT REQUIRED" />
+ <meta property="og:description" content="This platform doesn't pay me to provide metadata." />
+ <meta property="og:type" content="website" />
+ <meta property="og:url" content="https://matthewgraybosch.com/social.html" />
+ <meta property="og:image" content="https://matthewgraybosch.com/media/nonserviam.png" />
+ <meta name="twitter:card" content="summary_large_image" />
+ <meta name="twitter:title" content="402 PAYMENT REQUIRED" />
+ <meta name="twitter:description" content="This platform doesn't pay me to provide metadata." />
+ <meta name="twitter:image" content="https://matthewgraybosch.com/media/nonserviam.png" />
+ <meta name="twitter:image:alt" content="Non Serviam (I will not serve)" />
+ <meta name="twitter:creator" content="@MGraybosch" />
</head>
<body>
+ <div id="skip"><a href="#content">Skip Navigation and Headings</a></div>
<header>
- <h1><xsl:value-of select="/rss/channel/title" /></h1>
+ <h1>
+ <xsl:value-of select="/rss/channel/title" />
+ </h1>
<p>
- <i><xsl:value-of select="/rss/channel/description" /></i>
+ <i>
+ <xsl:value-of select="/rss/channel/description" />
+ </i>
</p>
</header>
- <xsl:for-each select="/rss/channel/item">
- <xsl:sort select="category" order="descending" />
- <span class="date">
- <xsl:value-of select="pubDate" />
- </span>
- <xsl:element name="a">
- <xsl:attribute name="href">
- <xsl:value-of select="link" />
- </xsl:attribute>
- <span>
- <xsl:value-of select="title" />
+
+ <nav>
+ <a href="/">Home</a>
+ · <a href="/about.html">About</a>
+ · <a href="/disclaimer.html">Disclaimer</a>
+ · <a href="/feed.xml">RSS</a>
+ </nav>
+
+ <main id="content">
+ <xsl:for-each select="/rss/channel/item">
+ <xsl:sort select="category" order="descending" />
+ <span class="date">
+ <xsl:value-of select="pubDate" />
</span>
- </xsl:element>
- </xsl:for-each>
+ <xsl:element name="a">
+ <xsl:attribute name="href">
+ <xsl:value-of select="link" />
+ </xsl:attribute>
+ <span>
+ <xsl:value-of select="title" />
+ </span>
+ </xsl:element>
+ </xsl:for-each>
+ </main>
+
+ <footer>
+ <p>
+ © 2022 Matthew Cambion, all rights reserved<br />
+ <small>
+ <i>kudos to <a href="mailto:contact@matthewgraybosch.com">contact@matthewgraybosch.com</a>; flames to /dev/null</i><br />
+ Powered by <a href="https://pblog.xyz" target="_blank">pblog</a><br />
+ Made with ♥ for a simpler web
+ </small>
+ </p>
+ <p><a href="#content">back to top</a></p>
+ </footer>
</body>
</html>In the current default rss.xsl, it’s impossible to
scroll blog/index.html because of the style Bradley applied
to the <html> element. This isn’t a big deal if
you’ve only got half a dozen posts or so, or if you aren’t using
responsive font sizes to take advantage of large, high-DPI displays, but
I was unpleasantly surprised when testing the new default XSL.
I also wanted to include a nav menu under the header, so I needed to tweak the header’s margin a bit – and I couldn’t resist adjusting the border color to match the text and other borders.
I’ve also added meta tags to include favicons, and a little 🖕 to
Facebook and Twitter. If they’re going to use proprietary meta tags
instead of the standard <title> and
<meta name="description"> tags, then I see nothing
wrong with making those tags useless.
Incidentally, HTTP 402 is a defined error code, but not implemented anywhere. It probably won’t get implemented since techies who might otherwise figure out how to implement reasonable payments for access to web pages are instead mucking around with cryptocurrency, blockchain, and the rest of the web3 nonsense.
Tweaking pblog.sh Itself
The shell script that does most of the work, pblog.sh is
made to be tweaked. At the very least you’ve got to adjust certain
variables to suit your needs. I’ve gone a bit beyond that, though, as
you might see from the diff below.
diff --git a/../pblog.xyz/pblog.sh b/pblog.sh
old mode 100644
new mode 100755
index 8a1e14b..73a508d
--- a/../pblog.xyz/pblog.sh
+++ b/pblog.sh
@@ -1,18 +1,16 @@
#!/bin/sh
# Site specific settings
-###################################################################################
-DOMAIN="https://pblog.xyz"
-TITLE="pblog.xyz"
-DESCRIPTION="Pandoc static blog generator"
-COPYRIGHT="Copyright 2022, Bradley Taunt"
-AUTHOR="hello@tdarb.org (Bradley Taunt)"
+DOMAIN="http://matthewgraybosch.com"
+TITLE="Matthew Cambion"
+DESCRIPTION="long-haired metalhead, out-of-print sf author, and grumpy old techie"
+COPYRIGHT="Copyright 2022, Matthew Cambion"
+AUTHOR="contact@matthewgraybosch.com (Matthew Cambion)"
OS="BSD" # "Linux" for Linux, "BSD" for BSD Systems (including MacOS)
-HTML_LANG="en_US" # Your document (HTML) language setting
+HTML_LANG="en" # Adding this for accessibility
INC_HEAD_FOOT=true # Automatically include the '_header.html' & '_footer.html' on all posts/pages
# Blog structure settings (most users should use these defaults)
-###################################################################################
TOC=true
SYNTAX=true
PAGES_DIR="pages/"
@@ -24,11 +22,13 @@ RSS_HTML="rss/"
OUTPUT="_output/"
TIME=$(date +"%T %Z")
TTL="60"
+WRAP=preserve
###################################################################################
# !WARNING!
# You probably don't need to tweak anything below this line. Edit at your own risk!
###################################################################################
+
if [[ $TOC = true ]]
then
TOC_TOGGLE="--toc";
@@ -44,16 +44,16 @@ if [[ $SYNTAX = true ]]
fi
# Create the RSS specific HTML versions for all posts (no DOCTYPE, Header elements)
-for i in $POSTS; do pandoc -M document-css=false --ascii --wrap=none -s $i -o ${i%.*}.html; done;
+for i in $POSTS; do pandoc -M document-css=false --ascii --wrap=$WRAP -s $i -o ${i%.*}.html; done;
rsync $POSTS_DIR*.html $RSS_HTML;
rm $POSTS_DIR*.html
# Create the web browser-focused HTML versions for all posts
if [[ $INC_HEAD_FOOT = true ]]
then
- for i in $POSTS; do pandoc --css=../style.css --ascii --metadata lang="$HTML_LANG" $TOC_TOGGLE $SYNTAX_TOGGLE --wrap=none -A _footer.html -B _header.html -s $i -o ${i%.*}.html; done;
+ for i in $POSTS; do pandoc --css=../style.css --template template.html --ascii --metadata sitetitle="$TITLE" --metadata lang="$HTML_LANG" $TOC_TOGGLE $SYNTAX_TOGGLE --wrap=$WRAP -H _head.html -A _footer.html -B _header.html -s $i -o ${i%.*}.html; done;
else
- for i in $POSTS; do pandoc --css=../style.css --ascii --metadata lang="$HTML_LANG" $TOC_TOGGLE $SYNTAX_TOGGLE --wrap=none -s $i -o ${i%.*}.html; done;
+ for i in $POSTS; do pandoc --css=../style.css --template template.html --ascii --metadata sitetitle="$TITLE" --metadata lang="$HTML_LANG" $TOC_TOGGLE $SYNTAX_TOGGLE --wrap=$WRAP -s $i -o ${i%.*}.html; done;
fi
rsync $POSTS_DIR*.html $OUTPUT$WEB_HTML;
rm $POSTS_DIR*.html
@@ -61,9 +61,11 @@ rm $POSTS_DIR*.html
# Create the web browser-focused HTML versions for all pages
if [[ $INC_HEAD_FOOT = true ]]
then
- for i in $PAGES; do pandoc --css=style.css --ascii --metadata lang="$HTML_LANG" $TOC_TOGGLE $SYNTAX_TOGGLE --wrap=none -A _footer.html -B _header.html -s $i -o ${i%.*}.html; done;
+ for i in $PAGES; do pandoc --css=style.css --template template.html --ascii --metadata sitetitle="$TITLE" --metadata lang="$HTML_LANG" $TOC_TOGGLE $SYNTAX_TOGGLE --wrap=$WRAP -H _head.html -A _footer.html -B _header.html -s $i -o ${i%.*}.html; done;
+ pandoc --css=style.css --template template.html --ascii --metadata sitetitle="$TITLE" --metadata lang="$HTML_LANG" $SYNTAX_TOGGLE --wrap=$WRAP -H _head.html -A _footer.html -B _header.html -s ${PAGES_DIR}index.md -o ${PAGES_DIR}index.html
else
- for i in $PAGES; do pandoc --css=style.css --ascii --metadata lang="$HTML_LANG" $TOC_TOGGLE $SYNTAX_TOGGLE --wrap=none -s $i -o ${i%.*}.html; done;
+ for i in $PAGES; do pandoc --css=style.css --template template.html --ascii --metadata sitetitle="$TITLE" --metadata lang="$HTML_LANG" $TOC_TOGGLE $SYNTAX_TOGGLE --wrap=$WRAP -s $i -o ${i%.*}.html; done;
+ pandoc --css=style.css --template template.html --ascii --metadata sitetitle="$TITLE" --metadata lang="$HTML_LANG" $SYNTAX_TOGGLE --wrap=$WRAP -s ${PAGES_DIR}index.md -o ${PAGES_DIR}index.html
fi
rsync $PAGES_DIR*.html $OUTPUT;
rm $PAGES_DIR*.html
@@ -110,4 +112,4 @@ echo "<item>
done
echo " </channel>
-</rss>";
+</rss>";I’ve made a few changes to suit my needs, but they might not be worth patching back into the original.
- I don’t want to do a find/replace when I want to change the
--wrapvalue passed topandoc, so I’ve added a$WRAPvariable. - I’m taking advantage of
pandoc’s ability to include arbitrary meta tags in<head>with the-Hoption to set favicon and social media tags. - I don’t want a table of contents on
/index.html(my home page), so I’m re-rendering it separately. - I’m passing in
$TITLEso that it gets included in<title>after the page’s title.
This last item is the complicated part. I’ll explain why shortly.
Custom HTML5 Templates
pandoc’s default HTML5 template doesn’t have a
sitetitle metadata variable. However, it’s possible to use
a custom template with the --template option.
Fortunately, custom templates are easy to generate. For HTML I needed
the following command:
pandoc -D html5 > template.htmlHere’s a diff for my template vs the default provided with
pandoc.
diff --git a/../pblog.xyz/template.html b/template.html
index 9699b85..f7ba274 100644
--- a/../pblog.xyz/template.html
+++ b/template.html
@@ -16,7 +16,7 @@ $endif$
$if(description-meta)$
<meta name="description" content="$description-meta$" />
$endif$
- <title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title>
+ <title>$pagetitle$$if(sitetitle)$ – $sitetitle$$endif$</title>
<style>
$styles.html()$
</style>
@@ -26,14 +26,12 @@ $endfor$
$if(math)$
$math$
$endif$
- <!--[if lt IE 9]>
- <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
- <![endif]-->
$for(header-includes)$
$header-includes$
$endfor$
</head>
<body>
+<div id="skip"><a href="#content">Skip Navigation and Headings</a></div>
$for(include-before)$
$include-before$
$endfor$
@@ -59,7 +57,9 @@ $endif$
$table-of-contents$
</nav>
$endif$
+<main id="content">
$body$
+</main>
$for(include-after)$
$include-after$
$endfor$It wasn’t until I started using pandoc that it occurred
to me that one might prefix the page’s title – presumably with the
site’s title or domain name – instead of appending that after
the page title. I don’t think it would look right in a browser tab, so
I’ve changed it.
I also noticed a reference to a HTML5 polyfill for IE 9. If that version of Microsoft Internet Explorer still got support, I might have left that in despite taking pride in not using JavaScript on personal websites. However, I doubt IE 9 is used outside of North Korea, and my understanding is that now that IE 11 is no longer supported, its usage dropped to nothing outside South Korea and inadequately budgeted government offices here in the US – and people working in the latter have no business visiting my website at work.
The addition of the <main> element isn’t strictly
necessary, but it makes adding a “back to top” link easy. Likewise, I
can add a skip navigaton link later on. I should probably do
that now and update the diffs.
It didn’t take that long. I can’t seem to tab to the skip nav link on my Mac, but it’s invisible in Firefox and Safari but still shows up in Lynx. (Because if your website doesn’t work in Lynx it just doesn’t work.)
Now that I think of it, I could have put the skip nav link in the
_header.html include, but I want to make sure it’s there
regardless of the contents of that file.
More on Creating “Skip Nav” Links
WebAIM has more information about creating “skip nav” links and making them invisible in graphical browsers.
Diff Files
All of the diff files I’ve included in this post are available to download.
Questions/Comments?
If you have any questions or comments about this post, feel free to
email me. You can also hit me up on Twitter, but you’re more
likely to get a response sooner by email.