config: change directory and use ESM
This commit is contained in:
parent
a5073e4c19
commit
b7c2308091
32 changed files with 392 additions and 298 deletions
|
|
@ -1,28 +0,0 @@
|
|||
/** All blog posts as a collection. */
|
||||
const getAllPosts = collection => {
|
||||
const posts = collection.getFilteredByGlob('./src/posts/**/*.md');
|
||||
return posts.reverse();
|
||||
};
|
||||
|
||||
/** All markdown files as a collection for sitemap.xml */
|
||||
const onlyMarkdown = collection => {
|
||||
return collection.getFilteredByGlob('./src/**/*.md');
|
||||
};
|
||||
|
||||
/** All tags from all posts as a collection. */
|
||||
const tagList = collection => {
|
||||
const tagsSet = new Set();
|
||||
collection.getAll().forEach(item => {
|
||||
if (!item.data.tags) return;
|
||||
item.data.tags
|
||||
.filter(tag => !['posts', 'docs', 'all'].includes(tag))
|
||||
.forEach(tag => tagsSet.add(tag));
|
||||
});
|
||||
return Array.from(tagsSet).sort();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getAllPosts,
|
||||
onlyMarkdown,
|
||||
tagList
|
||||
};
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
const dayjs = require('dayjs');
|
||||
const CleanCSS = require('clean-css');
|
||||
const site = require('../../src/_data/meta');
|
||||
const {throwIfNotType} = require('../utils');
|
||||
const esbuild = require('esbuild');
|
||||
|
||||
/** Removes all tags from an HTML string. */
|
||||
const stripHtml = str => {
|
||||
throwIfNotType(str, 'string');
|
||||
return str.replace(/<[^>]+>/g, '');
|
||||
};
|
||||
|
||||
/** Formats the given string as an absolute url. */
|
||||
const toAbsoluteUrl = url => {
|
||||
throwIfNotType(url, 'string');
|
||||
// Replace trailing slash, e.g., site.com/ => site.com
|
||||
const siteUrl = site.url.replace(/\/$/, '');
|
||||
// Replace starting slash, e.g., /path/ => path/
|
||||
const relativeUrl = url.replace(/^\//, '');
|
||||
|
||||
return `${siteUrl}/${relativeUrl}`;
|
||||
};
|
||||
|
||||
/** Converts the given date string to ISO8610 format. */
|
||||
const toISOString = dateString => dayjs(dateString).toISOString();
|
||||
|
||||
/** Formats a date using dayjs's conventions: https://day.js.org/docs/en/display/format */
|
||||
const formatDate = (date, format) => dayjs(date).format(format);
|
||||
|
||||
const minifyCss = code => new CleanCSS({}).minify(code).styles;
|
||||
|
||||
const minifyJs = async (code, ...rest) => {
|
||||
const callback = rest.pop();
|
||||
const cacheKey = rest.length > 0 ? rest[0] : null;
|
||||
|
||||
try {
|
||||
if (cacheKey && jsminCache.hasOwnProperty(cacheKey)) {
|
||||
const cacheValue = await Promise.resolve(jsminCache[cacheKey]); // Wait for the data, wrapped in a resolved promise in case the original value already was resolved
|
||||
callback(null, cacheValue.code); // Access the code property of the cached value
|
||||
} else {
|
||||
const minified = esbuild.transform(code, {
|
||||
minify: true
|
||||
});
|
||||
if (cacheKey) {
|
||||
jsminCache[cacheKey] = minified; // Store the promise which has the minified output (an object with a code property)
|
||||
}
|
||||
callback(null, (await minified).code); // Await and use the return value in the callback
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('jsmin error: ', err);
|
||||
callback(null, code); // Fail gracefully.
|
||||
}
|
||||
};
|
||||
|
||||
// source: https://github.com/bnijenhuis/bnijenhuis-nl/blob/main/.eleventy.js
|
||||
const splitlines = (input, maxCharLength) => {
|
||||
const parts = input.split(' ');
|
||||
const lines = parts.reduce(function (acc, cur) {
|
||||
if (!acc.length) {
|
||||
return [cur];
|
||||
}
|
||||
|
||||
let lastOne = acc[acc.length - 1];
|
||||
|
||||
if (lastOne.length + cur.length > maxCharLength) {
|
||||
return [...acc, cur];
|
||||
}
|
||||
|
||||
acc[acc.length - 1] = lastOne + ' ' + cur;
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return lines;
|
||||
};
|
||||
|
||||
const shuffleArray = array => {
|
||||
return array.sort(() => Math.random() - 0.5);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
toISOString,
|
||||
formatDate,
|
||||
toAbsoluteUrl,
|
||||
stripHtml,
|
||||
minifyCss,
|
||||
minifyJs,
|
||||
splitlines,
|
||||
shuffleArray
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// Because Nunjucks's include doesn't like CSS with "{#". Source: https://github.com/nhoizey/pack11ty/blob/781248b92480701208f69e2161165e58d79a23ee/src/_11ty/shortcodes/include_raw.js
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
let memoizedIncludes = {};
|
||||
|
||||
const includeRaw = file => {
|
||||
if (file in memoizedIncludes) {
|
||||
return memoizedIncludes[file];
|
||||
} else {
|
||||
let content = fs.readFileSync(file, 'utf8');
|
||||
memoizedIncludes[file] = content;
|
||||
return content;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = includeRaw;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
const imageShortcode = require('./image');
|
||||
const includeRaw = require('./includeRaw');
|
||||
const liteYoutube = require('./youtube-lite');
|
||||
module.exports = {
|
||||
imageShortcode,
|
||||
includeRaw,
|
||||
liteYoutube
|
||||
};
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
const liteYoutube = (id, label) => {
|
||||
return `
|
||||
<div class="youtube-embed"> <lite-youtube videoid="${id}" style="background-image: url('https://i.ytimg.com/vi/${id}/hqdefault.jpg');">
|
||||
<button type="button" class="lty-playbtn">
|
||||
<span class="lyt-visually-hidden">${label}</span>
|
||||
</button>
|
||||
</lite-youtube></div>
|
||||
`;
|
||||
};
|
||||
module.exports = liteYoutube;
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// CSS and JavaScript as first-class citizens in Eleventy: https://pepelsbey.dev/articles/eleventy-css-js/
|
||||
|
||||
const postcss = require('postcss');
|
||||
const postcssImport = require('postcss-import');
|
||||
const postcssImportExtGlob = require('postcss-import-ext-glob');
|
||||
const tailwindcss = require('tailwindcss');
|
||||
const postcssRelativeColorSyntax = require('@csstools/postcss-relative-color-syntax');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const cssnano = require('cssnano');
|
||||
|
||||
module.exports = eleventyConfig => {
|
||||
eleventyConfig.addTemplateFormats('css');
|
||||
|
||||
eleventyConfig.addExtension('css', {
|
||||
outputFileExtension: 'css',
|
||||
compile: async (content, path) => {
|
||||
if (path !== './src/assets/css/global.css') {
|
||||
return;
|
||||
}
|
||||
|
||||
return async () => {
|
||||
let output = await postcss([
|
||||
postcssImportExtGlob,
|
||||
postcssImport,
|
||||
tailwindcss,
|
||||
postcssRelativeColorSyntax({preserve: true}),
|
||||
autoprefixer,
|
||||
cssnano
|
||||
]).process(content, {
|
||||
from: path
|
||||
});
|
||||
|
||||
return output.css;
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
const esbuild = require('esbuild');
|
||||
|
||||
module.exports = eleventyConfig => {
|
||||
eleventyConfig.addTemplateFormats('js');
|
||||
|
||||
eleventyConfig.addExtension('js', {
|
||||
outputFileExtension: 'js',
|
||||
compile: async (content, path) => {
|
||||
if (!path.startsWith('./src/assets/scripts/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === './src/assets/scripts/theme-toggle.js') {
|
||||
await esbuild.build({
|
||||
target: 'es2020',
|
||||
entryPoints: [path],
|
||||
outfile: './src/_includes/theme-toggle-inline.js',
|
||||
bundle: true,
|
||||
minify: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return async () => {
|
||||
let output = await esbuild.build({
|
||||
target: 'es2020',
|
||||
entryPoints: [path],
|
||||
minify: true,
|
||||
bundle: true,
|
||||
write: false
|
||||
});
|
||||
|
||||
return output.outputFiles[0].text;
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
const slugify = require('slugify');
|
||||
|
||||
/** Converts string to a slug form. */
|
||||
const slugifyString = str => {
|
||||
return slugify(str, {
|
||||
replacement: '-',
|
||||
remove: /[#,&,+()$~%.'":*¿?¡!<>{}]/g,
|
||||
lower: true
|
||||
});
|
||||
};
|
||||
|
||||
/** throw an error if the provided argument is not of the expected. */
|
||||
const throwIfNotType = (arg, expectedType) => {
|
||||
if (typeof arg !== expectedType) {
|
||||
throw new Error(
|
||||
`Expected argument of type ${expectedType} but instead got ${arg} (${typeof arg})`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
slugifyString,
|
||||
throwIfNotType
|
||||
};
|
||||
19
src/_config/collections.js
Normal file
19
src/_config/collections.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/** All blog posts as a collection. */
|
||||
export const getAllPosts = collection => {
|
||||
return collection.getFilteredByGlob('./src/posts/**/*.md').reverse();
|
||||
};
|
||||
|
||||
/** All markdown files as a collection for sitemap.xml */
|
||||
export const onlyMarkdown = collection => {
|
||||
return collection.getFilteredByGlob('./src/**/*.md');
|
||||
};
|
||||
|
||||
/** All tags from all posts as a collection - excluding custom collections */
|
||||
export const tagList = collection => {
|
||||
const tagsSet = new Set();
|
||||
collection.getAll().forEach(item => {
|
||||
if (!item.data.tags) return;
|
||||
item.data.tags.filter(tag => !['posts', 'docs', 'all'].includes(tag)).forEach(tag => tagsSet.add(tag));
|
||||
});
|
||||
return Array.from(tagsSet).sort();
|
||||
};
|
||||
5
src/_config/events.js
Normal file
5
src/_config/events.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import {svgToJpeg} from './events/svg-to-jpeg.js';
|
||||
|
||||
export default {
|
||||
svgToJpeg
|
||||
};
|
||||
|
|
@ -1,24 +1,17 @@
|
|||
// https://bnijenhuis.nl/notes/automatically-generate-open-graph-images-in-eleventy/
|
||||
// https://github.com/sophiekoonin/localghost/blob/main/src/plugins/og-to-png.js
|
||||
// converts SVG to JPEG for open graph images
|
||||
|
||||
const fsPromises = require('fs/promises');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Image = require('@11ty/eleventy-img');
|
||||
import {promises as fsPromises, existsSync} from 'fs';
|
||||
import path from 'node:path';
|
||||
import Image from '@11ty/eleventy-img';
|
||||
const ogImagesDir = './src/assets/og-images';
|
||||
|
||||
const svgToJpeg = async function () {
|
||||
export const svgToJpeg = async function () {
|
||||
const socialPreviewImagesDir = 'dist/assets/og-images/';
|
||||
const files = await fsPromises.readdir(socialPreviewImagesDir);
|
||||
if (files.length > 0) {
|
||||
files.forEach(function (filename) {
|
||||
files.forEach(async function (filename) {
|
||||
const outputFilename = filename.substring(0, filename.length - 4);
|
||||
if (
|
||||
filename.endsWith('.svg') & !fs.existsSync(path.join(ogImagesDir, outputFilename))
|
||||
) {
|
||||
if (filename.endsWith('.svg') & !existsSync(path.join(ogImagesDir, outputFilename))) {
|
||||
const imageUrl = socialPreviewImagesDir + filename;
|
||||
Image(imageUrl, {
|
||||
await Image(imageUrl, {
|
||||
formats: ['jpeg'],
|
||||
outputDir: ogImagesDir,
|
||||
filenameFormat: function (id, src, width, format, options) {
|
||||
|
|
@ -31,7 +24,3 @@ const svgToJpeg = async function () {
|
|||
console.log('⚠ No social images found');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
svgToJpeg
|
||||
};
|
||||
20
src/_config/filters.js
Normal file
20
src/_config/filters.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import {toISOString, formatDate} from './filters/dates.js';
|
||||
import {markdownFormat} from './filters/markdown-format.js';
|
||||
import {shuffleArray} from './filters/sort-random.js';
|
||||
import {sortAlphabetically} from './filters/sort-alphabetic.js';
|
||||
import {splitlines} from './filters/splitlines.js';
|
||||
import {striptags} from './filters/striptags.js';
|
||||
import {toAbsoluteUrl} from './filters/to-absolute-url.js';
|
||||
import {slugifyString} from './filters/slugify.js';
|
||||
|
||||
export default {
|
||||
toISOString,
|
||||
formatDate,
|
||||
markdownFormat,
|
||||
splitlines,
|
||||
striptags,
|
||||
toAbsoluteUrl,
|
||||
shuffleArray,
|
||||
sortAlphabetically,
|
||||
slugifyString
|
||||
};
|
||||
7
src/_config/filters/dates.js
Normal file
7
src/_config/filters/dates.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import dayjs from 'dayjs';
|
||||
|
||||
/** Converts the given date string to ISO8610 format. */
|
||||
export const toISOString = dateString => dayjs(dateString).toISOString();
|
||||
|
||||
/** Formats a date using dayjs's conventions: https://day.js.org/docs/en/display/format */
|
||||
export const formatDate = (date, format) => dayjs(date).format(format);
|
||||
9
src/_config/filters/markdown-format.js
Normal file
9
src/_config/filters/markdown-format.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// by Chris Burnell: https://chrisburnell.com/article/some-eleventy-filters/#markdown-format
|
||||
|
||||
import markdownParser from 'markdown-it';
|
||||
|
||||
const markdown = markdownParser();
|
||||
|
||||
export const markdownFormat = string => {
|
||||
return markdown.render(string);
|
||||
};
|
||||
10
src/_config/filters/slugify.js
Normal file
10
src/_config/filters/slugify.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import slugify from 'slugify';
|
||||
|
||||
/** Converts string to a slug form. */
|
||||
export const slugifyString = str => {
|
||||
return slugify(str, {
|
||||
replacement: '-',
|
||||
remove: /[#,&,+()$~%.'":*¿?¡!<>{}]/g,
|
||||
lower: true
|
||||
});
|
||||
};
|
||||
7
src/_config/filters/sort-alphabetic.js
Normal file
7
src/_config/filters/sort-alphabetic.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export const sortAlphabetically = array => {
|
||||
return array.sort((a, b) => {
|
||||
if (a.data.title < b.data.title) return -1;
|
||||
if (a.data.title > b.data.title) return 1;
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
3
src/_config/filters/sort-random.js
Normal file
3
src/_config/filters/sort-random.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const shuffleArray = array => {
|
||||
return array.sort(() => Math.random() - 0.5);
|
||||
};
|
||||
20
src/_config/filters/splitlines.js
Normal file
20
src/_config/filters/splitlines.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export const splitlines = (input, maxCharLength) => {
|
||||
const parts = input.split(' ');
|
||||
const lines = parts.reduce(function (acc, cur) {
|
||||
if (!acc.length) {
|
||||
return [cur];
|
||||
}
|
||||
|
||||
let lastOne = acc[acc.length - 1];
|
||||
|
||||
if (lastOne.length + cur.length > maxCharLength) {
|
||||
return [...acc, cur];
|
||||
}
|
||||
|
||||
acc[acc.length - 1] = lastOne + ' ' + cur;
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return lines;
|
||||
};
|
||||
3
src/_config/filters/striptags.js
Normal file
3
src/_config/filters/striptags.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const striptags = string => {
|
||||
return string.replace(/<[^>]*>?/gm, '');
|
||||
};
|
||||
12
src/_config/filters/to-absolute-url.js
Normal file
12
src/_config/filters/to-absolute-url.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import {throwIfNotType} from '../utils/throw-if-not-type.js';
|
||||
import {url as site} from '../../_data/meta.js';
|
||||
|
||||
/** Formats the given string as an absolute url. */
|
||||
export const toAbsoluteUrl = url => {
|
||||
throwIfNotType(url, 'string');
|
||||
// Replace trailing slash, e.g., site.com/ => site.com
|
||||
const siteUrl = site.url.replace(/\/$/, '');
|
||||
// Replace starting slash, e.g., /path/ => path/
|
||||
const relativeUrl = url.replace(/^\//, '');
|
||||
return `${siteUrl}/${relativeUrl}`;
|
||||
};
|
||||
26
src/_config/plugins.js
Normal file
26
src/_config/plugins.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Eleventy
|
||||
import {EleventyRenderPlugin} from '@11ty/eleventy';
|
||||
import rss from '@11ty/eleventy-plugin-rss';
|
||||
import syntaxHighlight from '@11ty/eleventy-plugin-syntaxhighlight';
|
||||
import webc from '@11ty/eleventy-plugin-webc';
|
||||
|
||||
// custom
|
||||
import {markdownLib} from './plugins/markdown.js';
|
||||
|
||||
// Custom transforms
|
||||
import {htmlConfig} from './plugins/html-config.js';
|
||||
|
||||
// Custom template language
|
||||
import {cssConfig} from './plugins/css-config.js';
|
||||
import {jsConfig} from './plugins/js-config.js';
|
||||
|
||||
export default {
|
||||
EleventyRenderPlugin,
|
||||
rss,
|
||||
syntaxHighlight,
|
||||
webc,
|
||||
markdownLib,
|
||||
htmlConfig,
|
||||
cssConfig,
|
||||
jsConfig
|
||||
};
|
||||
49
src/_config/plugins/css-config.js
Normal file
49
src/_config/plugins/css-config.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import postcss from 'postcss';
|
||||
import postcssImport from 'postcss-import';
|
||||
import postcssImportExtGlob from 'postcss-import-ext-glob';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import cssnano from 'cssnano';
|
||||
|
||||
export const cssConfig = eleventyConfig => {
|
||||
eleventyConfig.addTemplateFormats('css');
|
||||
|
||||
eleventyConfig.addExtension('css', {
|
||||
outputFileExtension: 'css',
|
||||
compile: async (inputContent, inputPath) => {
|
||||
const paths = [];
|
||||
if (inputPath.endsWith('/src/assets/css/global/global.css')) {
|
||||
paths.push('dist/assets/css/global.css');
|
||||
paths.push('src/_includes/css/global.css'); // Assuming you might want to keep naming consistent for ease
|
||||
} else if (inputPath.includes('/src/assets/css/bundle/')) {
|
||||
const baseName = path.basename(inputPath);
|
||||
paths.push(`src/_includes/css/${baseName}`);
|
||||
} else if (inputPath.includes('/src/assets/css/components/')) {
|
||||
const baseName = path.basename(inputPath);
|
||||
paths.push(`dist/assets/css/components/${baseName}`);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
return async () => {
|
||||
let result = await postcss([
|
||||
postcssImportExtGlob,
|
||||
postcssImport,
|
||||
tailwindcss,
|
||||
autoprefixer,
|
||||
cssnano
|
||||
]).process(inputContent, {from: inputPath});
|
||||
|
||||
// Write the output to all specified paths
|
||||
for (const outputPath of paths) {
|
||||
await fs.mkdir(path.dirname(outputPath), {recursive: true});
|
||||
await fs.writeFile(outputPath, result.css);
|
||||
}
|
||||
|
||||
return result.css;
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
const htmlmin = require('html-minifier-terser');
|
||||
import htmlmin from 'html-minifier-terser';
|
||||
|
||||
const isProduction = process.env.ELEVENTY_ENV === 'production';
|
||||
|
||||
module.exports = eleventyConfig => {
|
||||
export const htmlConfig = eleventyConfig => {
|
||||
eleventyConfig.addTransform('html-minify', (content, path) => {
|
||||
if (path && path.endsWith('.html') && isProduction) {
|
||||
return htmlmin.minify(content, {
|
||||
45
src/_config/plugins/js-config.js
Normal file
45
src/_config/plugins/js-config.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import esbuild from 'esbuild';
|
||||
import path from 'node:path';
|
||||
|
||||
export const jsConfig = eleventyConfig => {
|
||||
eleventyConfig.addTemplateFormats('js');
|
||||
|
||||
eleventyConfig.addExtension('js', {
|
||||
outputFileExtension: 'js',
|
||||
compile: async (content, inputPath) => {
|
||||
// Skip processing if not in the designated scripts directories
|
||||
if (!inputPath.startsWith('./src/assets/scripts/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inline scripts processing
|
||||
if (inputPath.startsWith('./src/assets/scripts/bundle/')) {
|
||||
const filename = path.basename(inputPath);
|
||||
const outputFilename = filename;
|
||||
const outputPath = `./src/_includes/scripts/${outputFilename}`;
|
||||
|
||||
await esbuild.build({
|
||||
target: 'es2020',
|
||||
entryPoints: [inputPath],
|
||||
outfile: outputPath,
|
||||
bundle: true,
|
||||
minify: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Default handling for other scripts, excluding inline scripts
|
||||
return async () => {
|
||||
let output = await esbuild.build({
|
||||
target: 'es2020',
|
||||
entryPoints: [inputPath],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
write: false
|
||||
});
|
||||
|
||||
return output.outputFiles[0].text;
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
const markdownIt = require('markdown-it');
|
||||
const markdownItPrism = require('markdown-it-prism');
|
||||
const markdownItAnchor = require('markdown-it-anchor');
|
||||
const markdownItClass = require('@toycode/markdown-it-class');
|
||||
const markdownItLinkAttributes = require('markdown-it-link-attributes');
|
||||
const markdownItEmoji = require('markdown-it-emoji').full;
|
||||
const markdownItEleventyImg = require('markdown-it-eleventy-img');
|
||||
const markdownItFootnote = require('markdown-it-footnote');
|
||||
const markdownitMark = require('markdown-it-mark');
|
||||
const markdownitAbbr = require('markdown-it-abbr');
|
||||
const {slugifyString} = require('../utils');
|
||||
const path = require('path');
|
||||
import markdownIt from 'markdown-it';
|
||||
import markdownItPrism from 'markdown-it-prism';
|
||||
import markdownItAnchor from 'markdown-it-anchor';
|
||||
import markdownItClass from '@toycode/markdown-it-class';
|
||||
import markdownItLinkAttributes from 'markdown-it-link-attributes';
|
||||
import {full as markdownItEmoji} from 'markdown-it-emoji';
|
||||
import markdownItEleventyImg from 'markdown-it-eleventy-img';
|
||||
import markdownItFootnote from 'markdown-it-footnote';
|
||||
import markdownitMark from 'markdown-it-mark';
|
||||
import markdownitAbbr from 'markdown-it-abbr';
|
||||
import {slugifyString} from '../filters/slugify.js';
|
||||
import path from 'node:path';
|
||||
|
||||
const markdownLib = markdownIt({
|
||||
export const markdownLib = markdownIt({
|
||||
html: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
|
|
@ -84,5 +84,3 @@ const markdownLib = markdownIt({
|
|||
.use(markdownItFootnote)
|
||||
.use(markdownitMark)
|
||||
.use(markdownitAbbr);
|
||||
|
||||
module.exports = markdownLib;
|
||||
|
|
@ -5,13 +5,13 @@ import path from 'node:path';
|
|||
const dataPath = './src/_data/builtwith.json';
|
||||
const screenshotDir = path.join(
|
||||
path.dirname(new URL(import.meta.url).pathname),
|
||||
'../../src/assets/images/screenshots'
|
||||
'../../assets/images/screenshots'
|
||||
);
|
||||
|
||||
async function fetchScreenshot(url, filePath) {
|
||||
const waitCondition = 'wait:2';
|
||||
const timeout = 'timeout:5';
|
||||
const apiUrl = `https://v1.screenshot.11ty.dev/${encodeURIComponent(url)}/opengraph/_${waitCondition}_${timeout}/`;
|
||||
const apiUrl = `https://v1.screenshot.11ty.dev/${encodeURIComponent(url)}/large/_${waitCondition}_${timeout}/`;
|
||||
|
||||
const buffer = await fetch(apiUrl, {
|
||||
duration: '1d',
|
||||
4
src/_config/shortcodes.js
Normal file
4
src/_config/shortcodes.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import {imageShortcode} from './shortcodes/image.js';
|
||||
import {svgShortcode} from './shortcodes/svg.js';
|
||||
|
||||
export default {imageShortcode, svgShortcode};
|
||||
|
|
@ -1,7 +1,12 @@
|
|||
const Image = require('@11ty/eleventy-img');
|
||||
const path = require('path');
|
||||
const htmlmin = require('html-minifier-terser');
|
||||
import Image from '@11ty/eleventy-img';
|
||||
import path from 'node:path';
|
||||
import htmlmin from 'html-minifier-terser';
|
||||
|
||||
/**
|
||||
* Converts an attribute map object to a string of HTML attributes.
|
||||
* @param {Object} attributeMap - The attribute map object.
|
||||
* @returns {string} - The string of HTML attributes.
|
||||
*/
|
||||
const stringifyAttributes = attributeMap => {
|
||||
return Object.entries(attributeMap)
|
||||
.map(([attribute, value]) => {
|
||||
|
|
@ -11,14 +16,26 @@ const stringifyAttributes = attributeMap => {
|
|||
.join(' ');
|
||||
};
|
||||
|
||||
const imageShortcode = async (
|
||||
/**
|
||||
* Generates an HTML image element with responsive images and optional caption.
|
||||
* @param {string} src - The path to the image source file.
|
||||
* @param {string} [alt=''] - The alternative text for the image.
|
||||
* @param {string} [caption=''] - The caption for the image.
|
||||
* @param {string} [loading='lazy'] - The loading attribute for the image.
|
||||
* @param {string} [className] - The CSS class name for the image element.
|
||||
* @param {string} [sizes='90vw'] - The sizes attribute for the image.
|
||||
* @param {number[]} [widths=[440, 650, 960, 1200]] - The widths for generating responsive images.
|
||||
* @param {string[]} [formats=['avif', 'webp', 'jpeg']] - The formats for generating responsive images.
|
||||
* @returns {string} - The HTML image element.
|
||||
*/
|
||||
export const imageShortcode = async (
|
||||
src,
|
||||
alt = '',
|
||||
caption,
|
||||
caption = '',
|
||||
loading = 'lazy',
|
||||
className,
|
||||
sizes = '90vw',
|
||||
widths = [440, 880, 1024, 1360],
|
||||
widths = [440, 650, 960, 1200],
|
||||
formats = ['avif', 'webp', 'jpeg']
|
||||
) => {
|
||||
const metadata = await Image(src, {
|
||||
|
|
@ -62,7 +79,7 @@ const imageShortcode = async (
|
|||
});
|
||||
|
||||
const imageElement = caption
|
||||
? `<figure class="flow ${className ? `${className}` : ''}">
|
||||
? `<figure slot="image" class="flow ${className ? `${className}` : ''}">
|
||||
<picture>
|
||||
${imageSources}
|
||||
<img
|
||||
|
|
@ -70,7 +87,7 @@ const imageShortcode = async (
|
|||
</picture>
|
||||
<figcaption>${caption}</figcaption>
|
||||
</figure>`
|
||||
: `<picture class="flow ${className ? `${className}` : ''}">
|
||||
: `<picture slot="image" class="flow ${className ? `${className}` : ''}">
|
||||
${imageSources}
|
||||
<img
|
||||
${imgageAttributes}>
|
||||
|
|
@ -78,5 +95,3 @@ const imageShortcode = async (
|
|||
|
||||
return htmlmin.minify(imageElement, {collapseWhitespace: true});
|
||||
};
|
||||
|
||||
module.exports = imageShortcode;
|
||||
23
src/_config/shortcodes/svg.js
Normal file
23
src/_config/shortcodes/svg.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Generates an optimized SVG shortcode with optional attributes.
|
||||
*
|
||||
* @param {string} svgName - The name of the SVG file (without the .svg extension).
|
||||
* @param {string} [ariaName=''] - The ARIA label for the SVG.
|
||||
* @param {string} [className=''] - The CSS class name for the SVG.
|
||||
* @param {string} [styleName=''] - The inline style for the SVG.
|
||||
* @returns {Promise<string>} The optimized SVG shortcode.
|
||||
*/
|
||||
|
||||
import {optimize} from 'svgo';
|
||||
import {readFileSync} from 'node:fs';
|
||||
|
||||
export const svgShortcode = async (svgName, ariaName = '', className = '', styleName = '') => {
|
||||
const svgData = readFileSync(`./src/assets/svg/${svgName}.svg`, 'utf8');
|
||||
|
||||
const {data} = await optimize(svgData);
|
||||
|
||||
return data.replace(
|
||||
/<svg(.*?)>/,
|
||||
`<svg$1 ${ariaName ? `aria-label="${ariaName}"` : 'aria-hidden="true"'} ${className ? `class="${className}"` : ''} ${styleName ? `style="${styleName}"` : ''} >`
|
||||
);
|
||||
};
|
||||
43
src/_config/utils/clamp-generator.js
Normal file
43
src/_config/utils/clamp-generator.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Credits:
|
||||
* - © Andy Bell - https://buildexcellentwebsit.es/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Takes an array of tokens and sends back and array of name
|
||||
* and clamp pairs for CSS fluid values.
|
||||
*
|
||||
* @param {array} tokens array of {name: string, min: number, max: number}
|
||||
* @returns {array} {name: string, value: string}
|
||||
*/
|
||||
|
||||
import viewports from '../../_data/designTokens/viewports.json';
|
||||
|
||||
export const clampGenerator = tokens => {
|
||||
const rootSize = 16;
|
||||
|
||||
return tokens.map(({name, min, max}) => {
|
||||
if (min === max) {
|
||||
return `${min / rootSize}rem`;
|
||||
}
|
||||
|
||||
// Convert the min and max sizes to rems
|
||||
const minSize = min / rootSize;
|
||||
const maxSize = max / rootSize;
|
||||
|
||||
// Convert the pixel viewport sizes into rems
|
||||
const minViewport = viewports.min / rootSize;
|
||||
const maxViewport = viewports.max / rootSize;
|
||||
|
||||
// Slope and intersection allow us to have a fluid value but also keep that sensible
|
||||
const slope = (maxSize - minSize) / (maxViewport - minViewport);
|
||||
const intersection = -1 * minViewport * slope + minSize;
|
||||
|
||||
return {
|
||||
name,
|
||||
value: `clamp(${minSize}rem, ${intersection.toFixed(2)}rem + ${(slope * 100).toFixed(
|
||||
2
|
||||
)}vw, ${maxSize}rem)`
|
||||
};
|
||||
});
|
||||
};
|
||||
13
src/_config/utils/throw-if-not-type.js
Normal file
13
src/_config/utils/throw-if-not-type.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Throws an error if the argument is not of the expected type.
|
||||
*
|
||||
* @param {*} arg - The argument to check the type of.
|
||||
* @param {string} expectedType - The expected type of the argument.
|
||||
* @throws {Error} If the argument is not of the expected type.
|
||||
*/
|
||||
|
||||
export const throwIfNotType = (arg, expectedType) => {
|
||||
if (typeof arg !== expectedType) {
|
||||
throw new Error(`Expected argument of type ${expectedType} but instead got ${arg} (${typeof arg})`);
|
||||
}
|
||||
};
|
||||
24
src/_config/utils/tokens-to-tailwind.js
Normal file
24
src/_config/utils/tokens-to-tailwind.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Credits:
|
||||
* - © Andy Bell - https://buildexcellentwebsit.es/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts human readable tokens into tailwind config friendly ones
|
||||
*
|
||||
* @param {array} tokens {name: string, value: any}
|
||||
* @return {object} {key, value}
|
||||
*/
|
||||
|
||||
import slugify from 'slugify';
|
||||
|
||||
export const tokensToTailwind = tokens => {
|
||||
const nameSlug = text => slugify(text, {lower: true});
|
||||
let response = {};
|
||||
|
||||
tokens.forEach(({name, value}) => {
|
||||
response[nameSlug(name)] = value;
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
Loading…
Reference in a new issue