|
| 1 | +// imports for the various eleventy plugins (navigation & image) |
| 2 | +const eleventyNavigationPlugin = require('@11ty/eleventy-navigation'); |
| 3 | +const { DateTime } = require('luxon'); |
| 4 | +const Image = require('@11ty/eleventy-img'); |
| 5 | +const path = require('path'); |
| 6 | +const util = require('util'); |
| 7 | +const MarkdownIt = require('markdown-it'); |
| 8 | +const md = new MarkdownIt(); |
| 9 | +// allows the use of {% image... %} to create responsive, optimised images |
| 10 | +// CHANGE DEFAULT MEDIA QUERIES AND WIDTHS |
| 11 | +async function imageShortcode(src, alt, className, loading, sizes = '(max-width: 600px) 400px, 850px, 1920px') { |
| 12 | + // don't pass an alt? chuck it out. passing an empty string is okay though |
| 13 | + if (alt === undefined) { |
| 14 | + throw new Error(`Missing \`alt\` on responsiveimage from: ${src}`); |
| 15 | + } |
| 16 | + |
| 17 | + // create the metadata for an optimised image |
| 18 | + let metadata = await Image(`${src}`, { |
| 19 | + widths: [200, 400, 850, 1920, 2500], |
| 20 | + formats: ['webp', 'jpeg'], |
| 21 | + urlPath: '/images/', |
| 22 | + outputDir: './public/images', |
| 23 | + filenameFormat: function (id, src, width, format, options) { |
| 24 | + const extension = path.extname(src); |
| 25 | + const name = path.basename(src, extension); |
| 26 | + return `${name}-${width}w.${format}`; |
| 27 | + }, |
| 28 | + }); |
| 29 | + |
| 30 | + // get the smallest and biggest image for picture/image attributes |
| 31 | + let lowsrc = metadata.jpeg[0]; |
| 32 | + let highsrc = metadata.jpeg[metadata.jpeg.length - 1]; |
| 33 | + |
| 34 | + // when {% image ... %} is used, this is what's returned |
| 35 | + return `<picture class="${className}"> |
| 36 | + ${Object.values(metadata) |
| 37 | + .map((imageFormat) => { |
| 38 | + return ` <source type="${imageFormat[0].sourceType}" srcset="${imageFormat |
| 39 | + .map((entry) => entry.srcset) |
| 40 | + .join(', ')}" sizes="${sizes}">`; |
| 41 | + }) |
| 42 | + .join('\n')} |
| 43 | + <img |
| 44 | + src="${lowsrc.url}" |
| 45 | + width="${highsrc.width}" |
| 46 | + height="${highsrc.height}" |
| 47 | + alt="${alt}" |
| 48 | + loading="${loading}" |
| 49 | + decoding="async"> |
| 50 | + </picture>`; |
| 51 | +} |
| 52 | + |
| 53 | +module.exports = function (eleventyConfig) { |
| 54 | + eleventyConfig.addFilter('markdown', function(value) { |
| 55 | + return md.render(value); |
| 56 | + }); |
| 57 | + // adds the navigation plugin for easy navs |
| 58 | + eleventyConfig.addPlugin(eleventyNavigationPlugin); |
| 59 | + |
| 60 | + eleventyConfig.addFilter('split', function(value, separator) { |
| 61 | + return value.split(separator); |
| 62 | + }); |
| 63 | + |
| 64 | + eleventyConfig.addFilter('sortMulti', function(arr, primaryAttr, primaryAsc = true, secondaryAttr, secondaryAsc = true) { |
| 65 | + return arr.slice().sort(function(a, b) { |
| 66 | + if (primaryAsc) { |
| 67 | + if (a.data[primaryAttr] > b.data[primaryAttr]) return -1; |
| 68 | + if (a.data[primaryAttr] < b.data[primaryAttr]) return 1; |
| 69 | + } else { |
| 70 | + if (a.data[primaryAttr] > b.data[primaryAttr]) return 1; |
| 71 | + if (a.data[primaryAttr] < b.data[primaryAttr]) return -1; |
| 72 | + } |
| 73 | + |
| 74 | + if (secondaryAsc) { |
| 75 | + if (a.data[secondaryAttr] < b.data[secondaryAttr]) return -1; |
| 76 | + if (a.data[secondaryAttr] > b.data[secondaryAttr]) return 1; |
| 77 | + } else { |
| 78 | + if (a.data[secondaryAttr] > b.data[secondaryAttr]) return 1; |
| 79 | + if (a.data[secondaryAttr] < b.data[secondaryAttr]) return -1; |
| 80 | + } |
| 81 | + |
| 82 | + return 0; |
| 83 | + }); |
| 84 | + }); |
| 85 | + |
| 86 | + eleventyConfig.addFilter('highlightAuthors', function(publicationAuthor, team) { |
| 87 | + // Check that team is defined and is an array |
| 88 | + if (!Array.isArray(team)) { |
| 89 | + return publicationAuthor; // Return the original publicationAuthor if team is not valid |
| 90 | + } |
| 91 | + |
| 92 | + // Extract memberName from each team member and convert to lower case for case-insensitive comparison |
| 93 | + const teamMemberNames = team.map(member => member.data.memberName ? member.data.memberName.toLowerCase() : ''); |
| 94 | + |
| 95 | + const authorNames = publicationAuthor.split(',').map(name => name.trim()); |
| 96 | + let updatedContent = ''; // Initialize empty string to accumulate new HTML content |
| 97 | + |
| 98 | + // Process each author name for potential matches |
| 99 | + authorNames.forEach((name, index) => { |
| 100 | + const lowerCaseName = name.toLowerCase(); |
| 101 | + const matchingMember = teamMemberNames.find(memberName => |
| 102 | + lowerCaseName.includes(memberName) |
| 103 | + ); |
| 104 | + |
| 105 | + // If a matching team member is found, wrap the name in a span with style for red text |
| 106 | + if (matchingMember) { |
| 107 | + updatedContent += `<span class="cs-highlight-author">${name}</span>`; |
| 108 | + } else { |
| 109 | + updatedContent += name; // No match found, keep original name |
| 110 | + } |
| 111 | + |
| 112 | + // Add comma separator between names, except after the last name |
| 113 | + if (index < authorNames.length - 1) { |
| 114 | + updatedContent += ', '; |
| 115 | + } |
| 116 | + }); |
| 117 | + |
| 118 | + // Update the authorElement's HTML with the new content, including styled spans |
| 119 | + return updatedContent; |
| 120 | + }); |
| 121 | + |
| 122 | + eleventyConfig.addCollection("processedPublications", function(collection) { |
| 123 | + let tagCounts = {}; |
| 124 | + const publications = collection.getFilteredByGlob("./src/publications/*.md"); |
| 125 | + |
| 126 | + // Count occurrences of each tag |
| 127 | + publications.forEach(pub => { |
| 128 | + (pub.data.tags || []).forEach(tag => { |
| 129 | + tagCounts[tag] = (tagCounts[tag] || 0) + 1; |
| 130 | + }); |
| 131 | + }); |
| 132 | + |
| 133 | + // Return the tag counts for use in templates |
| 134 | + return tagCounts; |
| 135 | + }); |
| 136 | + |
| 137 | + eleventyConfig.addCollection("processedPublicationsJournalTypes", function(collection) { |
| 138 | + let journalTypes = {}; |
| 139 | + const publications = collection.getFilteredByGlob("./src/publications/*.md"); |
| 140 | + |
| 141 | + // Count occurrences of each tag |
| 142 | + publications.forEach(pub => { |
| 143 | + journalTypes[pub.data.publicationType] = (journalTypes[pub.data.publicationType] || 0) + 1; |
| 144 | + }); |
| 145 | + |
| 146 | + // Return the tag counts for use in templates |
| 147 | + return journalTypes; |
| 148 | + }); |
| 149 | + |
| 150 | + eleventyConfig.addCollection("processedTeams", function(collection) { |
| 151 | + let tagCounts = {}; |
| 152 | + const team = collection.getFilteredByGlob("./src/team/*.md"); |
| 153 | + |
| 154 | + // Count occurrences of each tag |
| 155 | + team.forEach(pub => { |
| 156 | + (pub.data.tags || []).forEach(tag => { |
| 157 | + tagCounts[tag] = (tagCounts[tag] || 0) + 1; |
| 158 | + }); |
| 159 | + }); |
| 160 | + |
| 161 | + // Return the tag counts for use in templates |
| 162 | + return tagCounts; |
| 163 | + }); |
| 164 | + |
| 165 | + eleventyConfig.addFilter('console', function(value) { |
| 166 | + const str = util.inspect(value); |
| 167 | + return `<div style="white-space: pre-wrap;">${unescape(str)}</div>;` |
| 168 | + }); |
| 169 | + |
| 170 | + // allows css, assets, robots.txt and CMS config files to be passed into /public |
| 171 | + eleventyConfig.addPassthroughCopy('./src/css/**/*.css'); |
| 172 | + eleventyConfig.addPassthroughCopy('./src/assets'); |
| 173 | + eleventyConfig.addPassthroughCopy('./src/admin'); |
| 174 | + eleventyConfig.addPassthroughCopy('./src/_redirects'); |
| 175 | + eleventyConfig.addPassthroughCopy({ './src/robots.txt': '/robots.txt' }); |
| 176 | + |
| 177 | + // open on npm start and watch CSS files for changes - doesn't trigger 11ty rebuild |
| 178 | + eleventyConfig.setBrowserSyncConfig({ |
| 179 | + open: true, |
| 180 | + files: './public/css/**/*.css', |
| 181 | + }); |
| 182 | + |
| 183 | + // allows the {% image %} shortcode to be used for optimised iamges (in webp if possible) |
| 184 | + eleventyConfig.addNunjucksAsyncShortcode('image', imageShortcode); |
| 185 | + |
| 186 | + // normally, 11ty will render dates on blog posts in full JSDate format (Fri Dec 02 18:00:00 GMT-0600). That's ugly |
| 187 | + // this filter allows dates to be converted into a normal, locale format. view the docs to learn more (https://moment.github.io/luxon/api-docs/index.html#datetime) |
| 188 | + eleventyConfig.addFilter('postDate', (dateObj) => { |
| 189 | + return DateTime.fromJSDate(dateObj).toLocaleString(DateTime.DATE_MED); |
| 190 | + }); |
| 191 | + |
| 192 | + return { |
| 193 | + dir: { |
| 194 | + input: 'src', |
| 195 | + includes: '_includes', |
| 196 | + layouts: "_layouts", |
| 197 | + output: 'public', |
| 198 | + }, |
| 199 | + // allows .html files to contain nunjucks templating language |
| 200 | + htmlTemplateEngine: 'njk', |
| 201 | + }; |
| 202 | +}; |
0 commit comments