|
|
|
const HTMLMinifier = require("html-minifier");
|
|
|
|
const CleanCSS = require("clean-css");
|
|
|
|
const UglifyES = require("uglify-es");
|
|
|
|
const MarkdownIt = require("markdown-it");
|
|
|
|
const JSYaml = require("js-yaml");
|
|
|
|
require('intl');
|
|
|
|
|
|
|
|
const markdownIt = MarkdownIt({
|
|
|
|
html: true,
|
|
|
|
breaks: true,
|
|
|
|
linkify: true
|
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = function(eleventyConfig) {
|
|
|
|
eleventyConfig.setLibrary("md", markdownIt);
|
|
|
|
|
|
|
|
eleventyConfig.addPassthroughCopy({
|
|
|
|
"static/favicon": ".",
|
|
|
|
"static/img": "img"
|
|
|
|
});
|
|
|
|
|
|
|
|
eleventyConfig.addFilter("monthAndYear", function(date) {
|
|
|
|
return date.toLocaleDateString("en-US", {
|
|
|
|
year: "numeric",
|
|
|
|
month: "short"
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Creates a child object with a new or overridden property without affecting
|
|
|
|
// the original.
|
|
|
|
function inheritAndAdd(base, newProperties) {
|
|
|
|
return Object.assign(Object.create(base), newProperties);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Identifies highlights in this array.
|
|
|
|
//
|
|
|
|
// itemList is an array of objects with "id" (string) properties
|
|
|
|
// filter is a specification for how to filter this list, which can be:
|
|
|
|
// * true or "all", in which case all items are highlights
|
|
|
|
// * some falsy value, in which case no items are highlights
|
|
|
|
// * an array containing item IDs which are highlights and others are
|
|
|
|
// lowlights
|
|
|
|
// * an array containing "all" and item IDs each prepended by "-", where
|
|
|
|
// negated IDs are lowlights and others are highlights
|
|
|
|
// * an object, in which case its keys are used as an array (as above)m
|
|
|
|
// regardless of their values
|
|
|
|
// highlightsFirst is an optional boolean; if true, highlights are listed
|
|
|
|
// first, then lowlights, though order is preserved within those groups. If
|
|
|
|
// false or not specified, order is the same as the input.
|
|
|
|
// Returns an array of objects, each of which has a one-to-one correspondence
|
|
|
|
// with an input item, using that object as its prototype and adding a
|
|
|
|
// boolean "highlight" property.
|
|
|
|
function identifyHighlights(itemList, filter) {
|
|
|
|
if (!Array.isArray(itemList)) {
|
|
|
|
return itemList;
|
|
|
|
}
|
|
|
|
if (!Array.isArray(filter)) {
|
|
|
|
if (filter === true || filter === "all") {
|
|
|
|
filter = ["all"]
|
|
|
|
} else if (typeof filter === "object") {
|
|
|
|
filter = Object.keys(filter);
|
|
|
|
} else {
|
|
|
|
filter = [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return itemList.map(function(item) {
|
|
|
|
if ((filter.includes("all")
|
|
|
|
&& !filter.includes("-" + item.id))
|
|
|
|
|| filter.includes(item.id)) {
|
|
|
|
return inheritAndAdd(item, {highlight: true});
|
|
|
|
} else {
|
|
|
|
return inheritAndAdd(item, {highlight: false});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
eleventyConfig.addFilter("identifyHighlights", identifyHighlights);
|
|
|
|
|
|
|
|
// Identifies highlights in this and each of the child layers.
|
|
|
|
//
|
|
|
|
// itemList is an array of objects with
|
|
|
|
// "id" (string) and headAttribute (list of object) properties.
|
|
|
|
// filter is a specification for how to filter this list and its children:
|
|
|
|
// * true or "all", in which case all items on all layers are highlights
|
|
|
|
// * some falsy value, in which case all items on all layers are lowlights
|
|
|
|
// * an array containing item IDs, in which case all matching items on this
|
|
|
|
// layer are highlights, as well as all of their descendants, and all
|
|
|
|
// non-matching items on this layer and all of their descendants are
|
|
|
|
// lowlights
|
|
|
|
// * an array containing "all" and item IDs preceded by "-", in which case all
|
|
|
|
// negated IDs and their descendants are lowlights, and all others and
|
|
|
|
// their descendants are highlights
|
|
|
|
// * an object whose keys are used as an array (as above) to match this layer,
|
|
|
|
// but whose values are used as the value for filter when recursively
|
|
|
|
// calling on the value of headAttribute.
|
|
|
|
// If one of these keys exists, the child will be called with its value as
|
|
|
|
// the filter argument, in this order:
|
|
|
|
// * child.id
|
|
|
|
// * "-" + child.id
|
|
|
|
// * "all"
|
|
|
|
// * "-all"
|
|
|
|
// Otherwise, the child will be called with false.
|
|
|
|
// headAttribute, nextAttribute, and tailAttributes are strings; nextAttribute
|
|
|
|
// and tailAttributes are optional.
|
|
|
|
// Returns an array of objects, each of which has a one-to-one correspondence
|
|
|
|
// with an input item, using that object as its prototype and adding a
|
|
|
|
// boolean "highlight" property and a list of objects headAttribute property
|
|
|
|
// fulfilling the same type of contract as described here. Order is
|
|
|
|
// preserved within highlights and lowlights, but highlights are first in
|
|
|
|
// the list.
|
|
|
|
function identifyHighlightsRecursive(itemList, filter, headAttribute, nextAttribute, ...tailAttributes) {
|
|
|
|
if (Array.isArray(filter)) {
|
|
|
|
const newFilter = {};
|
|
|
|
filter.forEach(function(item) {
|
|
|
|
if (item.startsWith("-")) {
|
|
|
|
newFilter[item] = false;
|
|
|
|
} else {
|
|
|
|
newFilter[item] = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
filter = newFilter;
|
|
|
|
} else if (typeof filter !== "object"){
|
|
|
|
if (filter === true || filter === "all") {
|
|
|
|
filter = { "all": true };
|
|
|
|
} else {
|
|
|
|
filter = {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const highlightList = identifyHighlights(itemList, filter);
|
|
|
|
if (!headAttribute || !Array.isArray(highlightList)) {
|
|
|
|
return highlightList;
|
|
|
|
}
|
|
|
|
highlightList.forEach(function(item) {
|
|
|
|
if (!(headAttribute in item)) {
|
|
|
|
// can't recurse into an item which doesn't have our recursive property
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const children = item[headAttribute];
|
|
|
|
const match = [item.id, "-" + item.id, "all", "-all"].find((key) => key in filter);
|
|
|
|
const childFilter = match ? filter[match] : false;
|
|
|
|
// safe to modify in-place because we got this result back from
|
|
|
|
// identifyHighlights, which inherits from its input
|
|
|
|
item[headAttribute] = identifyHighlightsRecursive(children, childFilter, nextAttribute, ...tailAttributes);
|
|
|
|
});
|
|
|
|
return highlightList;
|
|
|
|
}
|
|
|
|
eleventyConfig.addFilter("identifyHighlightsRecursive", identifyHighlightsRecursive);
|
|
|
|
|
|
|
|
function sortHighlightsFirst(itemList) {
|
|
|
|
return itemList.filter((it) => it.highlight).concat(itemList.filter((it) => !it.highlight));
|
|
|
|
}
|
|
|
|
eleventyConfig.addFilter("sortHighlightsFirst", sortHighlightsFirst);
|
|
|
|
|
|
|
|
function identifyExperienceHighlights(roleList, filter) {
|
|
|
|
const expandedRoleList = roleList.map(function(role) {
|
|
|
|
const children = role.achievements
|
|
|
|
? role.achievements.slice()
|
|
|
|
: [];
|
|
|
|
const fullDescription = role.description || role.shortDescription;
|
|
|
|
const shortDescription = role.description && role.shortDescription;
|
|
|
|
const descriptionObject = {
|
|
|
|
id: "description",
|
|
|
|
full: fullDescription,
|
|
|
|
short: shortDescription,
|
|
|
|
highlight: false
|
|
|
|
};
|
|
|
|
children.push(descriptionObject);
|
|
|
|
return inheritAndAdd(role, {
|
|
|
|
description: descriptionObject,
|
|
|
|
shortDescription: null,
|
|
|
|
_children: children
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const highlightedRoleList = identifyHighlightsRecursive(expandedRoleList, filter, "_children");
|
|
|
|
highlightedRoleList.forEach(function(role) {
|
|
|
|
role.description = role._children[role._children.length - 1];
|
|
|
|
role.achievements = role._children.slice(0, role._children.length - 1);
|
|
|
|
});
|
|
|
|
return highlightedRoleList;
|
|
|
|
}
|
|
|
|
eleventyConfig.addFilter("identifyExperienceHighlights", identifyExperienceHighlights);
|
|
|
|
|
|
|
|
// Checks if there are any items with a truthy highlight property in itemList.
|
|
|
|
function hasHighlights(itemList) {
|
|
|
|
if (!Array.isArray(itemList)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return itemList.some(function(item) {
|
|
|
|
return item.highlight;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
eleventyConfig.addFilter("hasHighlights", hasHighlights);
|
|
|
|
|
|
|
|
// Checks if there are any items with a falsy highlight property in itemList.
|
|
|
|
function hasLowlights(itemList) {
|
|
|
|
if (!Array.isArray(itemList)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return itemList.some(function(item) {
|
|
|
|
return !item.highlight;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
eleventyConfig.addFilter("hasLowlights", hasLowlights);
|
|
|
|
|
|
|
|
// Checks if there are any items within this list or its children with a
|
|
|
|
// truthy highlight property.
|
|
|
|
function hasHighlightsRecursive(itemList, headAttribute, nextAttribute, ...tailAttributes) {
|
|
|
|
if (!Array.isArray(itemList)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return hasHighlights(itemList) || itemList.some(
|
|
|
|
(child) => headAttribute in child && hasHighlightsRecursive(
|
|
|
|
child[headAttribute], nextAttribute, ...tailAttributes));
|
|
|
|
}
|
|
|
|
eleventyConfig.addFilter("hasHighlightsRecursive", hasHighlightsRecursive);
|
|
|
|
|
|
|
|
// Checks if there are any items within this list or its children with a
|
|
|
|
// falsy highlight property.
|
|
|
|
function hasLowlightsRecursive(itemList, headAttribute, nextAttribute, ...tailAttributes) {
|
|
|
|
if (!Array.isArray(itemList)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return hasLowlights(itemList) || itemList.some(
|
|
|
|
(child) => headAttribute in child && hasLowlightsRecursive(
|
|
|
|
child[headAttribute], nextAttribute, ...tailAttributes));
|
|
|
|
}
|
|
|
|
eleventyConfig.addFilter("hasLowlightsRecursive", hasLowlightsRecursive);
|
|
|
|
|
|
|
|
eleventyConfig.addFilter("md", function(content) {
|
|
|
|
return markdownIt.render(content);
|
|
|
|
});
|
|
|
|
|
|
|
|
eleventyConfig.addTransform("minifyHTML", function(html, path) {
|
|
|
|
if(path && path.endsWith(".html")) {
|
|
|
|
return HTMLMinifier.minify(html, {
|
|
|
|
collapseWhitespace: true,
|
|
|
|
removeComments: true,
|
|
|
|
useShortDoctype: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return html;
|
|
|
|
});
|
|
|
|
|
|
|
|
eleventyConfig.addNunjucksAsyncFilter("minifyCSS", function(code, callback) {
|
|
|
|
return new CleanCSS({ level: 2, inline: ['all'] }).minify(code, (err, result) => {
|
|
|
|
if (err) {
|
|
|
|
callback(err, null);
|
|
|
|
} else {
|
|
|
|
callback(null, result.styles);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
eleventyConfig.addFilter("minifyJS", function(code) {
|
|
|
|
let minified = UglifyES.minify(code);
|
|
|
|
if(minified.error) {
|
|
|
|
console.log("UglifyES failed with an error: ", minified.error);
|
|
|
|
throw new Error("Javascript minification failure");
|
|
|
|
}
|
|
|
|
return minified.code;
|
|
|
|
});
|
|
|
|
|
|
|
|
eleventyConfig.addDataExtension("yaml", contents => JSYaml.safeLoad(contents));
|
|
|
|
|
|
|
|
return {
|
|
|
|
templateFormats: [
|
|
|
|
"html",
|
|
|
|
"md",
|
|
|
|
"njk"
|
|
|
|
],
|
|
|
|
|
|
|
|
pathPrefix: "",
|
|
|
|
markdownTemplateEngine: "njk",
|
|
|
|
htmlTemplateEngine: "njk",
|
|
|
|
dataTemplateEngine: "njk",
|
|
|
|
passthroughFileCopy: true,
|
|
|
|
dir: {
|
|
|
|
input: ".",
|
|
|
|
includes: "_includes",
|
|
|
|
data: "_data",
|
|
|
|
output: "_site"
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|