Add next / previous cards to posts

This commit is contained in:
Nathan Upchurch 2024-01-02 16:43:56 -06:00
parent bd2b505a5e
commit 3140ef0b72
16 changed files with 233 additions and 28 deletions

View File

@ -1,8 +1,17 @@
# nathanupchurch.com - Based on eleventy-base-blog v8
# nathanupchurch.com
My blog, based on the very helpful eleventy-base-blog v8.
![](https://upchur.ch/piwigo/i.php?/upload/2023/09/22/20230922131353-32037f24-la.png)
### Implementation Notes
## Documentation
- `content/blog/` has the blog posts but really they can live in any directory. They need only the `posts` tag to be included in the blog posts [collection](https://www.11ty.dev/docs/collections/).
- Use the `eleventyNavigation` key (via the [Eleventy Navigation plugin](https://www.11ty.dev/docs/plugins/navigation/)) in your front matter to add a template to the top level site navigation. This is in use on `content/index.njk` and `content/about/index.md`.
### Metadata
Site metadata such as author info, title, etc. lives in _data/metadata.js. Links on the /me page, and default post images are also configured here.
### How to add a cowsay to a post with the custom nunjucks filter
You can use the `cowsay` filter to output a captioned `<figure>` containing a copy of an output from the cowsay program. Instead of using the usual three backticks, this method is accessible to visually impaired users thanks to the automatic captioning. Here's how to do it:
1. Add a copy of the cowsay output you'd like to display to _data/cowList.js. Be sure and escape any backslashes.
2. Use the filter like this: `{{ cowList.name | cowsay | safe }}`.
The `safe` filter is necessary so that Eleventy doesn't sanitize our HTML.

24
_data/cowList.js Normal file
View File

@ -0,0 +1,24 @@
module.exports = {
onScience: `
_________________________________________
( Once, when the secrets of science were )
( the jealously guarded property of a )
( small priesthood, the common man had no )
( hope of mastering their arcane )
( complexities. Years of study in musty )
( classrooms were prerequisite to )
( obtaining even a dim, incoherent )
( knowledge of science. )
( )
( Today all that has changed: a dim, )
( incoherent knowledge of science is )
( available to anyone. )
( )
( -- Tom Weller, "Science Made Stupid" )
-----------------------------------------
o ^__^
o (oo)\\_______
(__)\\ )\\/\\
||----w |
|| ||`
}

View File

@ -6,6 +6,7 @@
<title>{{ title or metadata.title }}</title>
<link rel="icon" type="image/x-icon" href="/img/logo_favicon.svg">
<meta name="description" content="{{ description or metadata.description }}">
<meta name="robots" content="noai, noimageai">
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="{{ metadata.title }}">
<link rel="alternate" href="/feed/feed.json" type="application/json" title="{{ metadata.title }}">

View File

@ -17,14 +17,4 @@ layout: layouts/base.njk
{{ content | safe }}
{%- if collections.posts %}
{%- set previousPost = collections.posts | getPreviousCollectionItem %}
{%- set nextPost = collections.posts | getNextCollectionItem %}
{%- if nextPost or previousPost %}
<ul class="links-nextprev">
{%- if previousPost %}<li>Previous: <a href="{{ previousPost.url }}">{{ previousPost.data.title }}</a></li>{% endif %}
{%- if nextPost %}<li>Next: <a href="{{ nextPost.url }}">{{ nextPost.data.title }}</a></li>{% endif %}
</ul>
<p>Questions? Comments? <a href="../../me">contact me</a>.</p>
{%- endif %}
{%- endif %}
{% include "nextLast.njk" %}

47
_includes/nextLast.njk Normal file
View File

@ -0,0 +1,47 @@
{%- if collections.posts %}
{%- set previousPost = collections.posts | getPreviousCollectionItem %}
{%- set nextPost = collections.posts | getNextCollectionItem %}
{%- if nextPost or previousPost %}
<section class="links-nextprev">
{%- if previousPost %}
<article class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
<a href="{{ previousPost.url }}" class="postlist-link">
<div class="post-image-container">
<img class="post-image" {% if previousPost.data.imageURL %} src="{{ previousPost.data.imageURL }}" alt="{{ previousPost.data.imageAlt }}" {% else %} src="{{ metadata.defaultPostImageURL }}" alt="{{ metadata.defaultPostImageAlt }}"{% endif %}>
</div>
</a>
<div class="post-copy">
<a href="{{ previousPost.url }}" class="postlist-link">
<h3>
{% if previousPost.data.title %}<span>Previous Article:</span><br>{{ previousPost.data.title }}{% else %}<code>{{ previousPost.url }}</code>{% endif %}
</h3>
</a>
<time class="postlist-date" datetime="{{ previousPost.date | htmlDateString }}">{{ previousPost.date | readableDate("LLLL yyyy") }}</time>
{% if previousPost.data.synopsis %}<p>{{ previousPost.data.synopsis | truncate(150) | safe }}</p>{% else %}{{ previousPost.content | truncate(150) | safe }}{% endif %}
</div>
</article>
{% endif %}
{%- if nextPost %}
<article class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
<a href="{{ nextPost.url }}" class="postlist-link">
<div class="post-image-container">
<img class="post-image" {% if nextPost.data.imageURL %} src="{{ nextPost.data.imageURL }}" alt="{{ nextPost.data.imageAlt }}" {% else %} src="{{ metadata.defaultPostImageURL }}" alt="{{ metadata.defaultPostImageAlt }}"{% endif %}>
</div>
</a>
<div class="post-copy">
<a href="{{ nextPost.url }}" class="postlist-link">
<h3>
{% if nextPost.data.title %}<span>Next Article:</span><br>{{ nextPost.data.title }}{% else %}<code>{{ nextPost.url }}</code>{% endif %}
</h3>
</a>
<time class="postlist-date" datetime="{{ nextPost.date | htmlDateString }}">{{ nextPost.date | readableDate("LLLL yyyy") }}</time>
{% if nextPost.data.synopsis %}<p>{{ nextPost.data.synopsis | truncate(150) | safe }}</p>{% else %}{{ nextPost.content | truncate(150) | safe }}{% endif %}
</div>
</section>
{% endif %}
</div>
<p>Questions? Comments? <a href="../../me">contact me</a>.</p>
{%- endif %}
{%- endif %}

View File

@ -0,0 +1,13 @@
---
title: Cowsay of the Day — Science
description: An ASCII cow postulates on the state of science education in the modern world.
date: 2024-01-02
tags:
- Cowsay of the Day
synopsis: An ASCII cow postulates on the state of science education in the modern world.
imageURL: /img/cowsayOfTheDay.avif
imageAlt: An ASCII cow with a thought bubble containing the word wut
---
As a big-old nerd, I spend a lot of time in the terminal on my computer. When you spend a lot of time somewhere, you want it to be comfortable. As a part of making my terminal more homey, I've set it up to give me a random quote each time I start a new session, delivered, of course, by a cow. Here's today's cowsay of the day:
{{ cowList.onScience | cowsay | safe }}

15
content/blog/offline.md Normal file
View File

@ -0,0 +1,15 @@
---
title: Offline
description: A conversation with a colleague caused me to consider the consequences of online-only tooling.
date: 2023-10-20
tags:
- Quick Thoughts
synopsis: A conversation with a colleague caused me to consider the consequences of online-only tooling.
imageURL: /img/kenny-eliason-uq5RMAZdZG4-unsplash.webp
imageAlt: A server rack in the dark with colorful cables draped between the ports of servers and switches.
---
As part of a project investigating a potential new piece of software, I've been speaking with colleagues and contractors to determine which features they rely on to do their day-to-day tasks, as well as discover any wish-list items for a new platform. In one of these discussions with a colleague, we had covered her relatively simple use case, and moved on to discuss potential features that might be useful. At this juncture, she mentioned, somewhat apologetically, that should a particular workflow be translated to a new platform, it was important to be able to access data and documents offline.
I've long been irritated by the propensity of modern proprietary software to lock in its users with monthly subscriptions and online-only access, allowing them to shut off the supply, as it were, at a moment's notice should a billing cycle fail to complete to their satisfaction, or should they be momentarily unable to subject their user to data collection or surveillance. I'm also no fan of slow, web-based software devouring gigabyte after gigabyte of memory, while requiring at least two machines to operate — a client, and a server. Still, I was struck by my colleague's comment; it seems rare these days that your average computer user has any expectation left that *anything* ought to work without access to the internet. It's a travesty, really.
Compared to the experience of opening Adobe Illustrator or even Outlook on the computer issued by my day-job, the results of switching my personal machines and the workflows for my small business to exclusively FLOSS tooling has felt liberating — no waiting at splash-screens, advertising baked into the OS, or fans screaming from the combined load of endless electron apps competing for their share of system resources; best of all, from spreadsheets, to my IDE, to design files, in order to get work done, not a damned thing requires me to be forever online.

View File

@ -0,0 +1,39 @@
---
title: ADGs - Alternative Diet Guests and Foodservice
description: Breaking down the alternative-diet restaurant experience to offer some perspective and advice to foodservice professionals and proprietors.
date: 2023-10-03
tags:
- Vegan Cooking
- Restaurants
synopsis: Breaking down the alternative-diet restaurant experience to offer some perspective and advice to foodservice professionals and proprietors.
imageURL: /img/k8-sWEpcc0Rm0U-unsplash.webp
imageAlt: An overhead view of a restaurant interior showing guests sitting at tables with white tablecloths, eating pastries.
---
I've been a vegan for close to a decade. From washing dishes, slinging cocktails, and pouring latte art, to recipe development, hiring, compliance, and multi-location operations, I've also been around the block a few times when it comes to foodservice. So when I tell you that the vast majority of foodservice establishments of any stripe are utter and complete complete nightmares for people with alternative diets, I hope you'll take me at my word.
For much of human civilization, food has represented more than mere fuel. Sharing a meal is a cornerstone of social and family life for billions of us around the globe; we gather together to discuss our day, we do business, we express love, and we wax philosophical over plates, bowls, leaves, platters, and hands holding an incredible variety of foods. In the present day, a growing class of people are becoming separated from the labor of cooking, while continuing to partake in its life-giving fruits. As a result of privilege, or a lack thereof, millions have come to rely on restaurants, bars, coffee shops, food stalls and stands to sustain themselves, and to participate in the social act of eating with others. While the resultant decline in food preparation skills is troubling, it is perhaps equally concerning that, whereas food was once created as a labor of love for those close to us, decisions made in the kitchens that feed so many leave those of us with less common needs and preferences largely excluded from many establishments, and with them, the important social role that they have come to play.
## For those with alternative diets, restaurants can be a source of stress.
For me and others like me, visiting a restaurant can be a difficult experience. From front of house staff with no idea what allergens their menu items contain, who don't understand the difference between Coeliac Disease and veganism, or who forget to include important information such as allergies or religious dietary restrictions into orders, to back of house staff who are equally uneducated on alternative dietary needs, lack the flexibility of process or inventory necessary to be accommodating, or who simply don't care and either treat accommodating guests with alternative diets as an encumbrance, or even lie outright and serve food that doesn't meet a guest's philosophical, religious, or even medical requirements, many of us have complicated feelings about going out to eat.
I once worked with a CEO who often invited the management team to dinner. We went to some of the best regarded restaurants in Chicago, but more often than not, I would wind up with a plate of french fries (dry, as the truffle aioli naturally wasn't vegan) and a glass of whiskey. Sometimes I'd be accommodated with a meal in-earnest. Once, I received an under-seasoned plate of couscous and mushy diced vegetables, and on another occasion, an over-priced bowl of black-bean soup. If there did happen to be a vegan menu item, it would rarely contain any protein, leaving me hungry after the meal. What ought to have been an enjoyable evening with colleagues became a source of anxiety; failing to attend these events may have left me on the sidelines at work, but going along left me feeling excluded and wishing that I were in my home kitchen making something worth eating.
## We're everywhere, and we probably want to eat at your restaurant.
Over the years I've heard many restaurateurs, managers, and chefs balk at the idea of creating a vegan menu item, claiming that it would be a moot exercise as they don't receive any vegan custom. The fact is, however, that in almost every instance they had absolutely no idea how many of their guests complied with any sort of alternative diet. From the smallest towns to the largest cities, ADGs are everywhere. If you have any custom at all, you likely have ADGs. Groups such as vegans, vegetarians, those who keep halal or kosher, those with allergies or Coeliac Disease, and so on each comprise large numbers of people, let alone in combination. If any reasonable number of people live or work near your establishment, there are almost certainly people who wish they could eat there, so near to where they spend so much of their time, but cannot for reasons either real or perceived.
Vegans and others with alternative diets have years of bad experiences to hone our expectations; we've each mentally composed a vast list of red flags - telltale signs that an establishment is going to be more trouble than its worth. When seeing these red flags, we are likely to anticipate either outright hostility, ranging from a bad attitude to being deliberately served foods we cannot eat, a lack of knowledge from staff resulting in difficulty ordering and a low degree of trust that our meal will be safe to eat, or to simply be told that there are no options available. In these situations, we may either simply avoid an establishment, or if dragged in by friends or colleagues, stick to what we know is safe: a black coffee, a fruit bowl, or even some fries, sans aioli, and a glass of whiskey. In all likelihood, a number of tickets for $5 coffees at your establishment might have been tickets for a starter and an entrée had an ADG seen one or two suitable menu items, or even just felt as though they had a fair chance at being accommodated.
## ADG items don't just make your menu accessible to swathes of new customers, done well, they should also excite your regulars.
Besides being reductive, the assertion that it's pointless to implement a vegan menu item because the business simply isn't there also implies that ADG items will only be saleable to ADGs. I can't count the number of times I've been told by someone that they simply cannot tolerate vegan food; each time, I'm tempted to express my condolences for my conversation partner's inability to enjoy a baguette, fresh fruit, vegetables, romesco sauce, peanut butter, french fries, Oreos, Swedish Fish candy, hummus, baba ganouj, nuts, Ritz Crackers, ketchup, good dark chocolate, bagels, pretzels, most potato chips, microwave popcorn, and dried pasta, foccaccia, olives, et cetera. While vegan food may be perceived by some as a special category of food, foreign and strange, only to be consumed by yet stranger people in acts of corporal mortification, vegan food, and indeed food suitable for most ADGs, is all around us; most of us already enjoy it every day, in one way or another.
[![A banana-nut doughnut: a doughnut on a plate covered in white icing, crushed nuts, and cinnamon.](/img/banana-nut-doughnut.webp "Some strange vegan food, originally posted on [my pixelfed account](https://pixelfed.social/p/nathanu/393147142652113079).")](/img/banana-nut-doughnut.webp)
Not only is there nothing strange about the food, there is also nothing so terribly different about the people who eat it. ADGs expect all of the joy from food that anyone else does: a balance of salt, fat, and acid, contrasting textures, mouth-watering flavors and aromas, and protein to keep us feeling full and satisfied. Any dish that meets these expectations ought to be worth ordering for *any* guest. This is a tough point for many otherwise proud chefs and proprietors to swallow; all else being equal, the fact is that most competent ADG menu items should be seeing sales in line with other items. Further, ADG menu items can make a menu accessible to multiple groups at once; a vegan item is also accessible to Catholics during Lent, in most instances those who keep halal or kosher, people with red meat, egg, or dairy allergies, vegetarians, and pescetarians.
## Making good food is just the start.
Designing and executing a competent ADG item or two is a fine start, but it's just that. Establishing trust takes time and effort; there are operational and cultural shifts that need to take place before a restaurant can be considered truly accessible to ADGs. While that work takes place over time, beginning with one or two competent menu items is a low-risk way for most independent establishments and other foodservice SMEs to expand their customer base. Bodies and beliefs are infinitely varied; likewise, people have an inexhaustible number of reasons to choose an ADG accessible food. As the role of restaurants becomes less of an occasional treat and more of a daily necessity, investing in menu development with ADG accessibility as a goal is increasingly becoming a simple matter of good business sense.

View File

@ -3,7 +3,6 @@ const markdownIt = require("markdown-it");
const markdownItFootnote = require("markdown-it-footnote");
const markdownItAnchor = require("markdown-it-anchor");
const mdfigcaption = require('markdown-it-image-figures');
const pluginRss = require("@11ty/eleventy-plugin-rss");
const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const pluginBundle = require("@11ty/eleventy-plugin-bundle");
@ -16,6 +15,13 @@ const figoptions = {
module.exports = function(eleventyConfig) {
// Helper Functions
const multiReplace = (text, replacementTable) => {
let newText = text;
replacementTable.forEach(x => { newText = newText.replace(x[0], x[1]) });
return newText;
};
eleventyConfig.addWatchTarget("content/**/*.{svg,webp,png,jpeg}");
// Official plugins
@ -35,7 +41,37 @@ module.exports = function(eleventyConfig) {
eleventyConfig.addFilter('htmlDateString', (dateObj) => {
// dateObj input: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string
return DateTime.fromJSDate(dateObj, {zone: 'utc'}).toFormat('yyyy-LL-dd');
return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat('yyyy-LL-dd');
});
// Shortcodes
eleventyConfig.addNunjucksFilter("cowsay", cowText => {
const cowCaptionReplacementTable = [
[`
o ^__^
o (oo)\\_______
(__)\\ )\\/\\
||----w |
|| ||`, ''],
[/\(\s+/g, ''],
[/\s+\(/g, ''],
[/_{3,}/g, ''],
[/-{3,}/g, ''],
[/\s\)/g, ''],
[/\n/g, ''],
[/\s{2,}/g, ' '],
[/^ /, '']
];
return `
<figure>
<pre class="language-" role="img" aria-label="ASCII COW">${cowText}</pre>
<figcaption id="cow-caption">
A cow thinking: <em>${multiReplace(cowText, cowCaptionReplacementTable)}</em>. The cow is illustrated using
preformatted text characters.
</figcaption>
</figure>
`;
});
// Passthrough
@ -48,10 +84,10 @@ module.exports = function(eleventyConfig) {
// Get the first `n` elements of a collection.
eleventyConfig.addFilter("head", (array, n) => {
if(!Array.isArray(array) || array.length === 0) {
if (!Array.isArray(array) || array.length === 0) {
return [];
}
if( n < 0 ) {
if (n < 0) {
return array.slice(n);
}
@ -66,7 +102,7 @@ module.exports = function(eleventyConfig) {
// Return all the tags used in a collection
eleventyConfig.addFilter("getAllTags", collection => {
let tagSet = new Set();
for(let item of collection) {
for (let item of collection) {
(item.data.tags || []).forEach(tag => tagSet.add(tag));
}
return Array.from(tagSet);
@ -85,13 +121,13 @@ module.exports = function(eleventyConfig) {
symbol: "#",
ariaHidden: false, // Features to make your build faster (when you need them)
// If your passthrough copy gets heavy and cumbersome, add this line
// to emulate the file copy on the dev server. Learn more:
// https://www.11ty.dev/docs/copy/#emulate-passthrough-copy-during-serve
// If your passthrough copy gets heavy and cumbersome, add this line
// to emulate the file copy on the dev server. Learn more:
// https://www.11ty.dev/docs/copy/#emulate-passthrough-copy-during-serve
// eleventyConfig.setServerPassthroughCopyBehavior("passthrough");
// eleventyConfig.setServerPassthroughCopyBehavior("passthrough");
}),
level: [1,2,3,4],
level: [1, 2, 3, 4],
slugify: eleventyConfig.getFilter("slugify")
}).use(markdownItFootnote).use(mdfigcaption, figoptions);
});

View File

@ -12,9 +12,9 @@ pre[class*="language-"] {
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
-webkit-hyphens: none;
-moz-hyphens: none;

View File

@ -357,6 +357,12 @@ nav ul {
display: flex;
flex-flow: column wrap;
}
.post-copy a h3 span {
font-size: var(--font-s);
font-weight: var(--weight-normal);
text-transform: uppercase;
letter-spacing: .15rem;
}
.post-image {
width: 15rem;
height: 15rem;
@ -411,6 +417,10 @@ a.post-tag:hover {
padding-left: 0em;
}
.post-metadata time {
padding-left: .15rem;
}
/* Direct Links / Markdown Headers */
a.header-anchor {
font-style: normal;

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@ -1,2 +1,23 @@
User-agent: CCBot
Disallow: /
User-agent: ChatGPT-User
Disallow: /
User-agent: GPTBot
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: anthropic-ai
Disallow: /
User-agent: Omgilibot
Disallow: /
User-agent: Omgili
Disallow: /
User-agent: FacebookBot
Disallow: /