]>
Witch of Git - web/blog/blob - eleventy.config.js
1 import pluginRss
from "@11ty/eleventy-plugin-rss";
2 import pluginSyntaxHighlight
from "@11ty/eleventy-plugin-syntaxhighlight";
3 import uslug
from "uslug";
4 import anchor
from "markdown-it-anchor";
5 import markdownIt
from "markdown-it";
6 import markdownItFootnote
from "markdown-it-footnote";
7 import CleanCSS
from "clean-css";
8 import htmlMinifier
from "html-minifier-terser";
9 import util
from "util";
10 import { DateTime
} from "luxon";
12 const md
= markdownIt({
16 slugify
: s
=> uslug(s
),
18 permalinkBefore
: true,
19 permalinkClass
: "header-anchor c-sun dec-none",
20 }).use(markdownItFootnote
);
22 export default async
function (eleventyConfig
) {
23 eleventyConfig
.addDateParsing(parseDate
);
24 // @TODO: Update to the new virtual template setup?
25 eleventyConfig
.addPlugin(pluginRss
);
26 eleventyConfig
.addPlugin(pluginSyntaxHighlight
, {
27 errorOnInvalidLanguage
: true,
30 eleventyConfig
.setLibrary("md", md
);
32 eleventyConfig
.addTransform("html-minifier", htmlMinifierTransform
);
34 eleventyConfig
.addPassthroughCopy("img");
35 eleventyConfig
.addPassthroughCopy("static");
36 eleventyConfig
.addPassthroughCopy("stamps");
37 eleventyConfig
.addPassthroughCopy({ "favicon": "/" });
39 eleventyConfig
.addNunjucksShortcode("youtube", youtubeShortcode
);
40 eleventyConfig
.addPairedNunjucksShortcode("tweet", tweetShortcode
);
41 eleventyConfig
.addPairedShortcode("aside", asideShortcode
);
42 eleventyConfig
.addPairedShortcode("figure", figureShortcode
);
44 eleventyConfig
.addFilter("date", dateFilter
);
45 eleventyConfig
.addFilter("markdown", value
=> md
.renderInline(value
));
46 eleventyConfig
.addFilter("groupby", groupbyFilter
);
47 eleventyConfig
.addFilter("cssmin", css
=>
48 new CleanCSS({}).minify(css
).styles
);
49 eleventyConfig
.addFilter("debug", util
.inspect
);
50 eleventyConfig
.addFilter("toRfc3339", toRfc3339Filter
);
51 eleventyConfig
.addFilter("hasTime", hasTimeFilter
);
53 eleventyConfig
.setDataDeepMerge(true);
55 eleventyConfig
.addCollection("years", collection
=> {
56 const posts
= collection
.getFilteredByTag("posts");
57 const items
= groupby(posts
, item
=> item
.date
.getFullYear());
58 return items
.reduce((obj
, [k
, v
]) => (obj
[k
] = v
, obj
), {});
62 markdownTemplateEngine
: "njk",
67 * @param {{string | Date | DateTime}} anyDate
70 function dateTime(anyDate
) {
71 if (anyDate
instanceof Date
) {
72 return DateTime
.fromJSDate(anyDate
);
74 if (DateTime
.isDateTime(anyDate
)) {
77 let date
= parseDate(anyDate
);
78 if (date
== null) { throw Error(`failed to parse date ${anyDate}`); }
82 function dateFilter(date
, format
) {
83 return dateTime(date
).toFormat(format
);
86 function toRfc3339Filter(date
) {
87 return dateTime(date
).toISO({ suppressMilliseconds
: true });
90 function hasTimeFilter(date
) {
91 date
= dateTime(date
);
95 || date
.millisecond
!= 0;
99 * @param {string} dateText
100 * @returns {{DateTime | null}}
102 function parseDate(dateText
) {
104 if (!dateText
) dateText
= "";
106 "yyyy-MM-dd HH:mm:ss z",
110 for (const format
of formats
) {
111 const date
= DateTime
.fromFormat(dateText
, format
, { setZone
: true });
112 if (date
.isValid
) { return date
; }
119 function access(item
, path
) {
120 const segments
= path
.split(".");
121 for (const seg
of segments
) {
122 if (item
=== undefined) { return null; }
123 if (seg
.endsWith("()")) {
124 const method
= item
[seg
.slice(0, -2)];
125 if (method
=== undefined) { return null; }
126 item
= method
.bind(item
)();
134 function groupby(items
, keyFn
) {
136 for (const item
of items
) {
137 const key
= keyFn(item
);
138 if (results
.length
== 0 || key
!= results
[results
.length
-1][0]) {
139 results
.push([key
, [item
]]);
141 results
[results
.length
-1][1].push(item
);
147 function groupbyFilter(items
, path
) {
148 return groupby(items
, item
=> access(item
, path
));
151 function youtubeShortcode(items
, inWidth
= 560, inHeight
= 315) {
152 const width
= items
.width
|| inWidth
;
153 const height
= items
.height
|| inHeight
;
154 const allow
= items
.allow
|| "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
155 const border
= items
.border
|| "0";
156 const video
= items
.video
|| items
;
158 throw "Required argument 'video'.";
160 const src
= "https://www.youtube.com/embed/" + video
;
161 return `<div class="youtube row hcenter">
162 <iframe width="${width}" height="${height}" src="${src}"
163 frameborder="${border}" allow="${allow}" allowfullscreen></iframe>
167 function tweetShortcode(content
, items
) {
168 // @TODO: Handle parsing date
169 return `<div class="row hcenter">
170 <blockquote class="twitter-tweet">
171 <p lang="en" dir="ltr">${content}</p> — ${items.name} (@${items.at})
172 <a href="${items.link}">${items.date}</a>
174 <script async src="https://platform.twitter.com/widgets.js" charset="utf-8">
179 function asideShortcode(content
, style
='') {
180 const html
= md
.render(content
);
182 return `<aside class="${style}">${html}</aside>`;
184 return `<aside>${html}</aside>`;
188 function figureShortcode(content
, { src
, alt
}) {
189 const captionHtml
= md
.render(content
);
190 return `<figure class="col hcenter">
191 <img src="${src}" alt="${alt}">
192 <figcaption>${captionHtml}</figcaption>
196 function htmlMinifierTransform(content
, outputPath
) {
197 if (outputPath
.endsWith(".html")) {
198 return htmlMinifier
.minify(content
, {
199 useShortDoctype
: true,
200 removeComments
: true,
201 collapseWhitespace
: true,