Last active
January 26, 2021 01:03
-
-
Save ollicle/94485a2e4cda5e78d252aa43df98d661 to your computer and use it in GitHub Desktop.
11ty directory indexes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Some files and snippets | |
.eleventy.js | |
_includes/ | |
layouts/ | |
main.njk | |
dayIndex.njk | |
monthIndex.njk | |
yearIndex.njk | |
src/ | |
indexes/ | |
_day.html | |
_month.html | |
_year.html (the extension is not important) | |
news/ | |
a_post.md | |
b_post.md | |
… | |
utils/ | |
collections/ | |
index.js | |
postDayIndexes.js | |
postMonthIndexes.js | |
postYearList.js | |
## .eleventy.js | |
//… | |
const collections = require('./utils/collections/'); | |
module.exports = function(eleventyConfig) { | |
filters(eleventyConfig); | |
collections(eleventyConfig); | |
eleventyConfig.setTemplateFormats([ | |
'html', | |
'njk', | |
'md' | |
]); | |
return { | |
dir: { | |
input: 'src', | |
includes: '../_includes', | |
output: 'public' | |
} | |
}; | |
}; | |
## src/news/a_post.md | |
--- | |
title: "A post" | |
tags: | |
- news | |
--- | |
Hello world! | |
Note the `news` tag here, used to isolate this post from other types in the collections. | |
## utils/collections/index.js | |
//… | |
const getPostYearList = require("./postYearList"); | |
const getPostsByMonth = require("./postsByMonth"); | |
const getPostMonthIndexes = require("./postMonthIndexes"); | |
const getPostDayIndexes = require("./postDayIndexes"); | |
module.exports = function (eleventyConfig){ | |
//… | |
// Year indexes | |
eleventyConfig.addCollection("postYearList", function (collection){ | |
return getPostYearList(collection.getFilteredByTag('news')); | |
}); | |
// Year index listing | |
eleventyConfig.addCollection("monthPosts", function (collection) { | |
return getPostsByMonth(collection.getFilteredByTag('news')); | |
}); | |
// Month indexes | |
eleventyConfig.addCollection("postMonthIndexes", function (collection) { | |
return getPostMonthIndexes(collection.getFilteredByTag('news')); | |
}); | |
// Day indexes | |
eleventyConfig.addCollection("postDayIndexes", function (collection) { | |
return getPostDayIndexes(collection.getFilteredByTag('news')); | |
}); | |
}; | |
Note `collection.getFilteredByTag('news')` to filter the collections to only include news tagged posts. | |
## utils/collections/postMonthIndexes.js | |
// I’m sure moment is overkill, but we’re not shipping it so ¯\_(ツ)_/¯ | |
const moment = require("moment"); | |
const reducer = (dateFormatter, collated, item) => { | |
const yearName = dateFormatter.year(item.date); | |
const monthNumber = dateFormatter.monthNumber(item.date); | |
const monthName = dateFormatter.monthName(item.date); | |
const monthSlug = dateFormatter.monthSlug(item.date).toLowerCase(); | |
// { 2003_8: …} | |
const collatedMonths = {...collated}; | |
const existingMonthObject = collatedMonths[`${yearName}_${monthNumber}`]; | |
const monthObject = existingMonthObject | |
? {...existingMonthObject} | |
: { | |
year: yearName, | |
month: { | |
title: monthName, | |
slug: monthSlug | |
} | |
}; | |
const collatedPostsInMonth = (monthObject.posts && monthObject.posts.slice(0)) || []; | |
monthObject.posts = collatedPostsInMonth.concat(item); | |
collatedMonths[`${yearName}_${monthNumber}`] = monthObject; | |
return collatedMonths; | |
}; | |
function structurePosts(posts, dateFormatter) { | |
return posts.reduce(reducer.bind(null, dateFormatter), {}); | |
} | |
function makeDateFormatter(datePattern) { | |
return function (date) { | |
return moment(date).format(datePattern); | |
} | |
} | |
module.exports = collectionItems => { | |
const dateFormatter = { | |
year: makeDateFormatter("YYYY"), | |
monthNumber: makeDateFormatter("M"), | |
monthName: makeDateFormatter("MMMM"), | |
monthSlug: makeDateFormatter("MMM") | |
}; | |
return structurePosts(collectionItems, dateFormatter); | |
} | |
Spits out a collection that looks like | |
{ | |
2003_8: { | |
year: '2003', | |
month: { | |
name: 'August', | |
slug: 'aug' | |
}, | |
posts: [{…}] | |
}, | |
2003_9: {…} | |
} | |
The key `2003_8` is arbitrary, it not used anywhere other than as a means to collate the contained posts. | |
Note I’m using the format `aug` as a slug in my urls (see permalink below). | |
## src/indexes/_month.html | |
--- | |
layout: layouts/monthIndex.njk | |
pagination: | |
data: collections.postMonthIndexes | |
size: 1 | |
alias: index | |
resolve: values | |
permalink: "news/{{index.year}}/{{index.month.slug}}/index.html" | |
--- | |
The magic here is the `size: 1` plus the variables in the permalink. Resulting in a single index page from each item in the collection. | |
Possible gotcha: if you attempt to derive values from a date object in the permalink, the templating language used becomes important, e.g. I think mine may be defaulting to liquid for .html here. | |
Note the alias `index` defines the value in the layout from which the properties hang. | |
## _includes/layouts/monthIndex.njk (portion) | |
<h1>{{index.month.title}} {{index.year}}</h1> | |
<div class="article-list"> | |
{% for post in index.posts %} | |
<article> | |
<header> | |
<h2><a href="{{post.url}}">{{ post.data.title }}</a></h2> | |
<p class="datestamp">Posted <date>{{ post.date | dateFormat('D MMMM YYYY') }}</date></p> | |
</header> | |
<p> | |
{{ post.data.description }} | |
</p> | |
<p><a href="{{post.url}}">Read {{ post.data.title }} in full</a></p> | |
</article> | |
{% endfor %} | |
</div> | |
Note `index` is the aliased value for the collection. | |
## utils/collections/postYearList.js | |
My approach to the year indexes as it stands is a little convoluted, included here as example only. This collection generates a list of years in which posts exist. | |
const moment = require("moment"); | |
function removeDuplicates(items){ | |
return Array.from(new Set(items)); | |
} | |
function makeDateFormatter(datePattern) { | |
return function (date) { | |
return moment(date).format(datePattern); | |
} | |
} | |
function generatePostDateSet(posts, dateFormatter) { | |
const fomattedDates = posts.map(item => { | |
return dateFormatter(item.data.page.date); | |
}); | |
return removeDuplicates(fomattedDates); | |
} | |
module.exports = collectionItems => { | |
return generatePostDateSet(collectionItems, makeDateFormatter("YYYY")); | |
} | |
I’m iterating over the year index using the `year` alias to reference yet another collection. | |
## src/indexes/_year.html | |
--- | |
layout: layouts/yearIndex.njk | |
pagination: | |
data: collections.postYearList | |
size: 1 | |
alias: year | |
permalink: "{{year}}/index.html" | |
--- | |
## _includes/layouts/yearIndex.njk (portion) | |
<h1>{{year}}</h1> | |
<div class="month-list"> | |
{% for month in collections.monthPosts[year] %} | |
<div> | |
<h2><a href="{{month.slug}}/">{{ month.title }}</a></h2> | |
<section class="article-list article-list--depth-1"> | |
{% for post in month.posts %} | |
<article class="article-list__item"> | |
<h3><a href="{{post.url}}">{{ post.data.title }}</a></h3> | |
<p class="datestamp">Posted <date>{{ post.date | dateFormat('D MMMM YYYY') }}</date></p> | |
<p> | |
{{ post.data.description }} | |
</p> | |
<p><a href="{{post.url}}">Read {{ post.data.title }} in full</a></p> | |
</article> | |
{% endfor %} | |
</section> | |
</div> | |
{% endfor %} | |
</div> | |
Note `collections.monthPosts[year]` references another collection. | |
## utils/collections/postsByMonth.js | |
const moment = require("moment"); | |
function sortBy(prop, a, b) { | |
return a[prop] - b[prop]; | |
} | |
function makeDateFormatter(datePattern) { | |
return function (date) { | |
return moment(date).format(datePattern); | |
} | |
} | |
function arrayFromObjectProps(object, sorter) { | |
const result = []; | |
for (const prop in object) { | |
result.push(object[prop]); | |
} | |
return result.sort(sorter); | |
} | |
function arrayMonths(collated){ | |
const result = {}; | |
for (const year in collated) { | |
result[year] = arrayFromObjectProps(collated[year], sortBy.bind(null, 'number')); | |
} | |
return result; | |
} | |
const reducer = (dateFormatter, collated, item) => { | |
const yearName = dateFormatter.year(item.date); | |
const monthNumber = dateFormatter.monthNumber(item.date); | |
const monthName = dateFormatter.monthName(item.date); | |
const monthSlug = dateFormatter.monthSlug(item.date).toLowerCase(); | |
// { "2003": …} | |
const collatedYears = {...collated}; | |
// {…} | |
const existingYearObject = collatedYears[yearName]; | |
const collatedMonthsInYear = existingYearObject | |
? {...existingYearObject} | |
: {}; | |
// {"title": …} | |
const existingMonthObject = collatedMonthsInYear[monthNumber]; | |
const monthObject = existingMonthObject | |
? {...existingMonthObject} | |
: { | |
title: monthName, | |
slug: monthSlug, | |
number: +monthNumber | |
}; | |
// [{post},…] | |
const collatedPostsInMonth = (monthObject.posts && monthObject.posts.slice(0)) || []; | |
monthObject.posts = collatedPostsInMonth.concat(item); | |
collatedMonthsInYear[monthNumber] = monthObject; | |
collatedYears[yearName] = collatedMonthsInYear; | |
return collatedYears; | |
}; | |
function structurePosts(posts, dateFormatter) { | |
return arrayMonths(posts.reduce(reducer.bind(null, dateFormatter), {})); | |
} | |
module.exports = collectionItems => { | |
const dateFormatter = { | |
year: makeDateFormatter("YYYY"), | |
monthNumber: makeDateFormatter("M"), | |
monthName: makeDateFormatter("MMMM"), | |
monthSlug: makeDateFormatter("MMM") | |
}; | |
return structurePosts(collectionItems, dateFormatter); | |
} | |
Each item of the collection is an array of months with corresponding posts | |
"2003": [ | |
{ | |
"title": "August", | |
"slug": "aug", | |
// posts whose date matches the year *and* month - sorted by date | |
"posts": [ | |
{…}, | |
{…} | |
] | |
}, | |
{ | |
"title": "September", | |
"slug": "sep", | |
"posts": […] | |
} | |
], | |
"2004": […] | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment