-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path.eleventy.js
365 lines (281 loc) · 12.7 KB
/
.eleventy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
const cheerio = require('cheerio')
// Markdown-it
const MarkdownIt = require("markdown-it")
const markdownItAnchor = require("markdown-it-anchor")
const markdownItAttrs = require("markdown-it-attrs")
const markdownItBracketedSpans = require("markdown-it-bracketed-spans")
const markdownItClass = require("@toycode/markdown-it-class")
const markdownItDiv = require("markdown-it-div")
const markdownItHeaderSections = require('markdown-it-header-sections')
const markdownItModifyToken = require("markdown-it-modify-token")
const markdownItImplicitFigures = require("markdown-it-implicit-figures")
const markdownItMarks = require("markdown-it-mark")
const markdownItSub = require("markdown-it-sub")
const markdownItSup = require("markdown-it-sup")
// Filters
const dateFilter = require("nunjucks-date-filter")
const slugify = require("slugify")
// Plugins
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight")
// Transforms
const citations = require('./utils/transforms/citations')
const figures = require('./utils/transforms/figures')
const footnotes = require('./utils/transforms/footnotes')
const hangingPunctuation = require('./utils/transforms/hanging-punctuation')
const iframes = require('./utils/transforms/iframes')
const lightbox = require('./utils/transforms/lightbox')
const removeEmptyTableHeads = require('./utils/transforms/remove-empty-table-heads')
const removeTodos = require('./utils/transforms/remove-todos')
const stopMeasurementsWrapping = require('./utils/transforms/stop-measurements-wrapping')
const videos = require('./utils/transforms/videos')
// const tagAbbreviations = require('./utils/transforms/tag-abbreviations')
module.exports = (config) => {
/*
Eleventy works by:
1. Finds content files to process (convert to HTML). It calls these files "templates" (confusing name). We specify which file formats to treat as templates in `templateFormats`, in config options. I use .md (the format I write my posts in) and .njk (Nunjucks, the format I write my layouts (e.g. footer.njk) and stand-alone HTML files (e.g. index.njk) in.
2. Processes templates with template engines. Nunjucks files are processed with Nunjucks engine (etc). Markdown files are special because they can optionally be pre-processed by a second template engine, before they're processed as Markdown. If, for example, we want to use Nunjucks markup inside our Markdown files, we could specify `markdownTemplateEngine: "njk"` in our config options. Per docs: https://www.11ty.dev/docs/languages/#special-case-pairing-a-templating-engine-with-md-markdown and https://www.11ty.dev/docs/config/#default-template-engine-for-markdown-files.
3. Templates (content) are optionally wrapped in layouts. Layouts are "special templates that can be used to wrap other content". Per docs: https://www.11ty.dev/docs/layouts/. Layouts can be specified per the 11ty data cascade: https://www.11ty.dev/docs/data-cascade/. I specify the layout for posts using a Directory Data File (a file named posts.json, inside the posts/ directory). Per docs: https://www.11ty.dev/docs/data-template-dir/.
4. Filters: Allow us to modify content inside the templates. For example, to modify a variable, or the `content`. Per https://www.11ty.dev/docs/filters/
5. Transforms: Allow us to modify the template's out. Per https://www.11ty.dev/docs/config/#transforms.
*/
// ========================================================
// BEFORE BUILD
// ========================================================
// eleventyConfig.on('beforeBuild', async () => {
// })
// ========================================================
// CONFIGURE
// ========================================================
let startEleventy = function () {
return {
// Specify which types of templates should be processed
// https://www.11ty.dev/docs/config/#template-formats
templateFormats: ["md", "njk"],
// The template engine to pre-process markdown files.
// Markdown files run through this template engine before transforming to HTML.
// https://www.11ty.dev/docs/config/#default-template-engine-for-markdown-files
markdownTemplateEngine: "njk",
dataTemplateEngine: "njk",
// Directories
dir: {
// Top-level directory for templates. "." is default.
input: "src",
// Includes directory (files, partials, macros). These files are consumed by templates. Relative to input directory.
includes: "includes",
// Layouts directory. Relative to input directory.
layouts: "layouts",
// Global data files directory (data available to all templates). Relative to input directory. "All *.json and module.exports values from *.js files in this directory will be added into a global data object available to all templates." Per: https://www.11ty.dev/docs/data-global/
data: "data",
// Top-level directory for output.
output: "_site",
}
}
}
// -------- Ignore files -------- //
// Tell eleventy to ignore certain files.
// Am doing it here instead of an .eleventyIgnore file.
// Docs: https://www.11ty.dev/docs/ignores/
config.ignores.add("README.md")
// -------- Enable Deep Data Merge -------- //
// This feature allows additive tags (combine tags from multiple levels of the data cascade, instead of replacing them). It may have unintended side effects, however.
// Per: https://www.11ty.dev/docs/data-deep-merge/
// Per: https://www.11ty.dev/docs/data-cascade/
// As of 1.0, this is on by default.
config.setDataDeepMerge(true)
// -------- Passthrough file copy -------- //
// Copy files and directories to output directories
// NOTE: I've switched to using npm for this.
// -------- Filters -------- //
// Called by template engines (e.g. nunjucks)
// Return excerpts from content
config.addFilter("excerpt", (content, label) => {
if (!content) return
const $ = cheerio.load(content)
const excerpt = $(label)
if (excerpt) {
return excerpt
} else {
return
}
})
// Slugify urls, filenames, ids.
// Uses https://www.npmjs.com/package/slugify
// This will come built-in with Eleventy 1.0,
// per https://www.11ty.dev/docs/filters/slugify/
config.addFilter("slugify", (string) => {
return slugify(string, {
lower: true,
remove: /[\*+~.,()'"!:@]/g
})
})
// Format dates.
// Uses https://www.npmjs.com/package/nunjucks-date-filter
// And momentjs formats: https://momentjs.com/docs/#/displaying/format/
// E.g: Posted on {{ page.date | date("MMM D YYYY") }}.
// "Posted on Dec 28 2021"
config.addFilter("date", dateFilter)
// -------- Collections -------- //
// Define custom collections
// Portfolio
config.addCollection("portfolio", (collection) =>
collection.getFilteredByTags("ux")
.filter((post) => post.data.publish)
.sort((a, b) => b.data.year - a.data.year)
)
// Posts
config.addCollection("posts", (collection) =>
collection.getFilteredByTags("post")
// Remove posts marked 'publish: false'
.filter((post) => post.data.publish)
.sort((a, b) => b.data.date - a.data.date)
// Newest at top
// // Put 'highlight' posts at top
// .sort((a, b) => a.data.tags.includes("highlight") ? -1 : 1)
)
// Portfolio
config.addCollection("projects", (collection) =>
collection.getFilteredByTags("project")
.filter((post) => post.data.publish)
.sort((a, b) => b.data.year - a.data.year)
)
// Climate posts
config.addCollection("climate", (collection) =>
collection.getFilteredByTags("climate")
// Remove posts marked 'publish: false'
.filter((post) => post.data.publish)
// Sort alphabetically
// .sort((a, b) => {
// if (a.data.title > b.data.title) {
// return 1
// } else if (a.data.title < b.data.title) {
// return -1
// } else {
// return 0
// }
// })
// Put 'explainer' posts at top
.sort((a, b) => {
if (a.data.tags.includes("explainer")) {
return -1
} else {
return 1
}
})
// Put 'highlight' posts at top
.sort((a, b) => {
if (a.data.tags.includes("highlight")) {
return -1
} else {
return 1
}
})
)
// -------- Plugins -------- //
config.addPlugin(syntaxHighlight);
// ========================================================
// CONVERT MARKDOWN TO HTML
// ========================================================
// Here we configure markdown-it, the markdown parser used by 11ty.
// https://github.com/markdown-it/markdown-it
// Technically, we're specifying our own instance of markdown-it as 11ty's Markdown library. Per: https://www.11ty.dev/docs/languages/markdown/#optional-set-your-own-library-instance
const md = new MarkdownIt({
html: true,
breaks: true,
linkify: true,
typographer: true,
quotes: '“”‘’',
// markdownItModifyToken configuration
modifyToken: function(token, env) {
switch (token.type) {
case 'image': {
// Add `data-lightbox` attribute to images
token.attrObj['data-lightbox'] = true
// Set `data-res` of images
// If it's a splash image, it gets Large-Medium-Small
// Else, it gets Medium-Medium-Small
const resolution = token.attrObj['class']?.includes('splash') ? "LMS" : "MMS"
token.attrObj['data-res'] = resolution
}
}
}
})
// Improve permalinks with uslug
// Per docs: https://github.com/valeriangalliat/markdown-it-anchor#unicode-support
// const uslug = require('uslug')
// const uslugify = (s) => uslug(s)
// Add classes
// md.use(markdownItClass, {
// blockquote: "body-text",
// p: "body-text",
// ul: "body-text",
// ol: "body-text",
// figcaption: "caption-clr small-text"
// })
// Add marks with `==...===`
md.use(markdownItMarks)
// Add sub and sup tag support
md.use(markdownItSup)
md.use(markdownItSub)
// Add anchors to headers
md.use(markdownItAnchor, {
level: [2, 3],
slugify: (s) => slugify(s, {
lower: true,
remove: /[*+~.,()'"!:@]/g
}),
permalink: markdownItAnchor.permalink.headerLink({ safariReaderFix: true }),
tabIndex: false
// permalinkSymbol: svgAnchorIcon,
})
// Add block divs with `::: #warning`
md.use(markdownItDiv)
// Add spans with square brackets.
// Eg: paragraph with [a span]{.myClass}
md.use(markdownItBracketedSpans)
// Add classes, identifiers and attributes with curly brackets.
// Eg: {.class #identifier attr=value}
md.use(markdownItAttrs)
// Wrap headers in sections
md.use(markdownItHeaderSections)
// Render images occurring by itself in a paragraph as `<figure>< img ...></figure>`, similar to pandoc's implicit_figures
md.use(markdownItImplicitFigures, {
figcaption: true,
copyAttrs: 'class'
})
// Add data-lightbox attribute to figures
md.use(markdownItModifyToken)
// Add data-lightbox attribute to figures
// (Alternative to using markdown-it-modify-token plugin)
// md.renderer.rules.figure_open = function (tokens, idx, options, env, self) {
// tokens[idx].attrPush(['data-lightbox', true]); // add new attribute
// // Pass token to default renderer
// return self.renderToken(tokens, idx, options);
// }
// -------------- Register markdown-it as library with 11ty -------------- //
// This has to go after other Markdown-It configurations (IIUC)
config.setLibrary("md", md)
// ========================================================
// SHORTCODES
// ========================================================
config.addShortcode("year", () => `${new Date().getFullYear()}`)
// ========================================================
// TRANSFORMS
// ========================================================
// NOTE: Order matters for some of these, so best not to re-arrange.
config.addTransform("removeTodos", removeTodos)
config.addTransform("footnotes", footnotes)
config.addTransform("citations", citations)
config.addTransform("modifyIframes", iframes)
config.addTransform("videos", videos)
config.addTransform("figures", figures)
config.addTransform("lightbox", lightbox)
// eleventyConfig.addTransform("metaImage", metaImage)
config.addTransform("hangingPunctuation", hangingPunctuation)
config.addTransform("removeEmptyTableHeads", removeEmptyTableHeads)
config.addTransform("stopMeasurementsWrapping", stopMeasurementsWrapping)
// eleventyConfig.addTransform("tagAbbreviations", tagAbbreviations)
// ========================================================
// RETURN THE CONFIGURED ELEVENTY OBJECT
// ========================================================
return startEleventy()
}