]> Witch of Git - web/blog/blob - .eleventy.js
Minify inline CSS, move syntax colors to bottom of page
[web/blog] / .eleventy.js
1 const pluginRss = require("@11ty/eleventy-plugin-rss");
2 const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
3 const Nunjucks = require("nunjucks");
4 const dateFilter = require("nunjucks-date-filter");
5 const md = require("markdown-it")();
6 const CleanCSS = require("clean-css");
7
8 module.exports = (eleventyConfig) => {
9 eleventyConfig.addNunjucksFilter('date', dateFilter);
10 eleventyConfig.addPlugin(pluginRss);
11 eleventyConfig.addPlugin(pluginSyntaxHighlight);
12 eleventyConfig.addFilter("cssmin", code =>
13 new CleanCSS({}).minify(code).styles);
14
15 eleventyConfig.addPassthroughCopy("img");
16 eleventyConfig.addPassthroughCopy("static");
17
18 eleventyConfig.addNunjucksShortcode("youtube", youtubeShortcode);
19 eleventyConfig.addPairedNunjucksShortcode("tweet", tweetShortcode);
20 eleventyConfig.addPairedShortcode("aside", asideShortcode);
21 eleventyConfig.addPairedShortcode("figure", figureShortcode);
22
23 eleventyConfig.addFilter("markdown", value => md.renderInline(value));
24 eleventyConfig.addFilter("groupby", groupbyFilter);
25
26 eleventyConfig.addCollection("years", collection => {
27 const posts = collection.getFilteredByTag("posts");
28 const items = groupby(posts, item => item.date.getFullYear());
29 return items.reduce((obj, [k, v]) => (obj[k] = v, obj), {});
30 });
31
32 return {
33 markdownTemplateEngine: "njk",
34 };
35 };
36
37 function access(item, path) {
38 const segments = path.split(".");
39 for (const seg of segments) {
40 if (item === undefined) { return null; }
41 if (seg.endsWith("()")) {
42 const method = item[seg.slice(0, -2)];
43 if (method === undefined) { return null; }
44 item = method.bind(item)();
45 } else {
46 item = item[seg];
47 }
48 }
49 return item;
50 }
51
52 function groupby(items, keyFn) {
53 const results = [];
54 for (const item of items) {
55 const key = keyFn(item);
56 if (results.length == 0 || key != results[results.length-1][0]) {
57 results.push([key, [item]]);
58 } else {
59 results[results.length-1][1].push(item);
60 }
61 }
62 return results;
63 }
64
65 function groupbyFilter(items, path) {
66 return groupby(items, item => access(item, path));
67 }
68
69 function youtubeShortcode(items, inWidth = 560, inHeight = 315) {
70 const width = items.width || inWidth;
71 const height = items.height || inHeight;
72 const allow = items.allow || "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
73 const border = items.border || "0";
74 const video = items.video || items;
75 if (!video) {
76 throw "Required argument 'video'.";
77 }
78 const src = "https://www.youtube.com/embed/" + video;
79 return `<div class="row flex-center">
80 <iframe width="${width}" height="${height}" src="${src}"
81 frameborder="${border}" allow="${allow}" allowfullscreen></iframe>
82 </div>`;
83 }
84
85 function tweetShortcode(content, items) {
86 // @TODO: Handle parsing date
87 return `<div class="row flex-center">
88 <blockquote class="twitter-tweet">
89 <p lang="en" dir="ltr">${content}</p> &mdash; ${items.name} (@${items.at})
90 <a href="${items.link}">${items.date}</a>
91 </blockquote>
92 <script async src="https://platform.twitter.com/widgets.js" charset="utf-8">
93 </script>
94 </div>`;
95 }
96
97 function asideShortcode(content, style='') {
98 const html = md.render(content);
99 if (style) {
100 return `<aside class="${style}">${html}</aside>`;
101 } else {
102 return `<aside>${html}</aside>`;
103 }
104 }
105
106 function figureShortcode(content, { src, alt }) {
107 const captionHtml = md.render(content);
108 return `<figure class="col hcenter">
109 <img src="${src}" alt="${alt}">
110 <figcaption>${captionHtml}</figcaption>
111 </figure>`;
112 }