Skip to content

Instantly share code, notes, and snippets.

@ollicle
Last active January 26, 2021 01:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ollicle/94485a2e4cda5e78d252aa43df98d661 to your computer and use it in GitHub Desktop.
Save ollicle/94485a2e4cda5e78d252aa43df98d661 to your computer and use it in GitHub Desktop.
11ty directory indexes
# 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