From a68663b8b46e6fdd369eef415bfd0869ad0e2c2f Mon Sep 17 00:00:00 2001 From: Neil Brommer Date: Fri, 27 Oct 2023 14:59:22 -0700 Subject: [PATCH] Automatically set image size --- eleventy.config.js | 6 +- markdown-it-image-size.js | 110 +++++++++++++++ package-lock.json | 132 +++++++++++++++++- package.json | 4 +- ...onList.png => BasicDescriptionList_2x.png} | Bin ...List.png => ColumnsDescriptionList_2x.png} | Bin ...onList.png => IndexDescriptionList_2x.png} | Bin ...onList.png => MixedDescriptionList_2x.png} | Bin src/posts/DescriptionListStyling.md | 12 +- 9 files changed, 249 insertions(+), 15 deletions(-) create mode 100644 markdown-it-image-size.js rename src/img/{BasicDescriptionList.png => BasicDescriptionList_2x.png} (100%) rename src/img/{ColumnsDescriptionList.png => ColumnsDescriptionList_2x.png} (100%) rename src/img/{IndexDescriptionList.png => IndexDescriptionList_2x.png} (100%) rename src/img/{MixedDescriptionList.png => MixedDescriptionList_2x.png} (100%) diff --git a/eleventy.config.js b/eleventy.config.js index 98afeae..3936499 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -6,6 +6,7 @@ const eleventyDrafts = require("./eleventy.config.drafts"); const mdDefList = require("markdown-it-deflist"); const mdToc = require("markdown-it-table-of-contents"); const mdAnchor = require("markdown-it-anchor"); +const { markdownItImageSize } = require("./markdown-it-image-size"); const nunjucksDate = require("nunjucks-date"); const markdownIt = require("markdown-it"); @@ -22,12 +23,15 @@ function addEleventyPlugins(eleventyConfig) { } function configureMarkdown(eleventyConfig) { + console.log("markdownItImageSize", markdownItImageSize); + eleventyConfig.amendLibrary("md", mdLib => mdLib .use(mdDefList) .use(mdToc, { includeLevel: [ 1, 2, 3, 4, 5, 6 ] }) - .use(mdAnchor)); + .use(mdAnchor) + .use(markdownItImageSize)); } function addFilters(eleventyConfig) { diff --git a/markdown-it-image-size.js b/markdown-it-image-size.js new file mode 100644 index 0000000..c20541e --- /dev/null +++ b/markdown-it-image-size.js @@ -0,0 +1,110 @@ +const imageSize = require("image-size"); +const markdownIt = require("markdown-it"); +const fetch = require("sync-fetch"); + +function markdownItImageSize(md) { + md.renderer.rules.image = (tokens, index, options, env) => { + const token = tokens[index]; + const srcIndex = token.attrIndex("src"); + const imageUrl = token.attrs[srcIndex][1]; + const caption = md.utils.escapeHtml(token.content); + const otherAttributes = generateAttributes(md, token); + + const isExternalImage = + imageUrl.startsWith("http://") || + imageUrl.startsWith("https://") || + imageUrl.startsWith("//"); + + const isLocalAbsoluteUrl = imageUrl.startsWith("/"); + + let { width, height } = isExternalImage + ? getImageDimensionsFromExternalImage(imageUrl) + : getImageDimensions(`${isLocalAbsoluteUrl ? "./src" : ""}${imageUrl}`); + + let imageFileName = imageUrl + .replaceAll("\\", "/") + .split("/") + .pop(); + + let scaleMatches = Array.from(imageFileName.matchAll(/_([0-9])x/g)); + + if (scaleMatches.length > 0){ + let scale = scaleMatches.pop()[1]; + + width = width / scale; + height = height / scale; + } + + const dimensionsAttributes = width && height + ? ` width="${width}" height="${height}"` + : ""; + + return `${caption}`; + }; +} + +function generateAttributes(md, token) { + const ignore = ["src", "alt"]; + const escape = ["title"]; + + return token.attrs + .filter(([key]) => !ignore.includes(key)) + .map(([key, value]) => { + const escapeAttributeValue = escape.includes(key); + const finalValue = escapeAttributeValue + ? md.utils.escapeHtml(value) + : value; + + return `${key}="${finalValue}"`; + }) + .join(" "); +} + +const customPluginDefaults = { + getAbsPathFromEnv: (env) => { + const markdownPath = env?.page?.inputPath; // 11ty + + if (markdownPath) { + return markdownPath + .substring(0, markdownPath.lastIndexOf("/")) + .replace(/\/\.\//g, "/"); + } + + return undefined; + }, +}; + +function getImageDimensions(imageUrl, env) { + try { + const { width, height } = imageSize(imageUrl); + return { width, height }; + } catch (error) { + const isRelativePath = !imageUrl.startsWith("/"); + const inputPath = customPluginDefaults.getAbsPathFromEnv(env); + + if (isRelativePath && inputPath) { + return getImageDimensions(`${inputPath}/${imageUrl}`); + } + + console.error( + `markdown-it-image-size: Could not get dimensions of image with url ${imageUrl}.\n\n`, + error, + ); + + return { width: undefined, height: undefined }; + } +} + +function getImageDimensionsFromExternalImage(imageUrl) { + const isMissingProtocol = imageUrl.startsWith("//"); + + const response = fetch(isMissingProtocol ? `https:${imageUrl}` : imageUrl); + const buffer = response.buffer(); + const { width, height } = imageSize(buffer); + + return { width, height }; +} + +exports.markdownItImageSize = markdownItImageSize; diff --git a/package-lock.json b/package-lock.json index ebcc674..01448a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,13 @@ "dependencies": { "@11ty/eleventy-plugin-rss": "^1.2.0", "feather-icons": "^4.29.0", + "image-size": "^1.0.2", "markdown-it-anchor": "^8.6.7", "markdown-it-table-of-contents": "^0.6.0", "normalize.css": "^8.0.1", "nunjucks-date": "^1.5.0", - "prism-themes": "^1.9.0" + "prism-themes": "^1.9.0", + "sync-fetch": "^0.5.2" }, "devDependencies": { "@11ty/eleventy": "^2.0.0", @@ -1190,6 +1192,20 @@ "node": ">= 0.10" } }, + "node_modules/image-size": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", + "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1203,8 +1219,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-alphabetical": { "version": "1.0.4", @@ -1729,6 +1744,25 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2089,6 +2123,14 @@ "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", "dev": true }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dependencies": { + "inherits": "~2.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2374,6 +2416,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sync-fetch": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.5.2.tgz", + "integrity": "sha512-6gBqqkHrYvkH65WI2bzrDwrIKmt3U10s4Exnz3dYuE5Ah62FIfNv/F63inrNhu2Nyh3GH5f42GKU3RrSJoaUyQ==", + "dependencies": { + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -2401,6 +2454,11 @@ "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=", "dev": true }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -2437,6 +2495,20 @@ "node": ">=0.10.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3362,6 +3434,14 @@ "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-1.0.0.tgz", "integrity": "sha512-TScO04soylRN9i/QdOdgZyhydXg9z6XdaGzEyOgDKycePeDeTT4KvigjBcI+tgfTlieLWauGORMq5F1eIDa+1w==" }, + "image-size": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", + "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "requires": { + "queue": "6.0.2" + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -3375,8 +3455,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-alphabetical": { "version": "1.0.4", @@ -3773,6 +3852,14 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4077,6 +4164,14 @@ "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", "dev": true }, + "queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "requires": { + "inherits": "~2.0.3" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4269,6 +4364,14 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "sync-fetch": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.5.2.tgz", + "integrity": "sha512-6gBqqkHrYvkH65WI2bzrDwrIKmt3U10s4Exnz3dYuE5Ah62FIfNv/F63inrNhu2Nyh3GH5f42GKU3RrSJoaUyQ==", + "requires": { + "node-fetch": "^2.6.1" + } + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -4290,6 +4393,11 @@ "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=", "dev": true }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -4314,6 +4422,20 @@ "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=", "dev": true }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 8719bd3..93421ff 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,12 @@ "dependencies": { "@11ty/eleventy-plugin-rss": "^1.2.0", "feather-icons": "^4.29.0", + "image-size": "^1.0.2", "markdown-it-anchor": "^8.6.7", "markdown-it-table-of-contents": "^0.6.0", "normalize.css": "^8.0.1", "nunjucks-date": "^1.5.0", - "prism-themes": "^1.9.0" + "prism-themes": "^1.9.0", + "sync-fetch": "^0.5.2" } } diff --git a/src/img/BasicDescriptionList.png b/src/img/BasicDescriptionList_2x.png similarity index 100% rename from src/img/BasicDescriptionList.png rename to src/img/BasicDescriptionList_2x.png diff --git a/src/img/ColumnsDescriptionList.png b/src/img/ColumnsDescriptionList_2x.png similarity index 100% rename from src/img/ColumnsDescriptionList.png rename to src/img/ColumnsDescriptionList_2x.png diff --git a/src/img/IndexDescriptionList.png b/src/img/IndexDescriptionList_2x.png similarity index 100% rename from src/img/IndexDescriptionList.png rename to src/img/IndexDescriptionList_2x.png diff --git a/src/img/MixedDescriptionList.png b/src/img/MixedDescriptionList_2x.png similarity index 100% rename from src/img/MixedDescriptionList.png rename to src/img/MixedDescriptionList_2x.png diff --git a/src/posts/DescriptionListStyling.md b/src/posts/DescriptionListStyling.md index 181f1d9..2e97b06 100644 --- a/src/posts/DescriptionListStyling.md +++ b/src/posts/DescriptionListStyling.md @@ -10,8 +10,7 @@ The `
` element is useful for displaying metadata or a read only version of a These are some basic styles to use by default: -Basic Description List Styles +![Basic Description List Styles](/img/BasicDescriptionList_2x.png) ```html
@@ -52,8 +51,7 @@ dd + dd { This styling will put the `
` elements in one column and the `
` elements into another. This can cause issues on small screens, so it supports breakpoints where it will fall back to the default style. -Side-By-Side Columns Description List Styles +![Side-By-Side Columns Description List Styles](/img/ColumnsDescriptionList_2x.png) ```html
@@ -125,8 +123,7 @@ $wsu-breakpoint-map: ( You can also mix this with the default stacked style: -Mixed Description List Styles +![Mixed Description List Styles](/img/MixedDescriptionList_2x.png) ```html
@@ -150,8 +147,7 @@ You can also mix this with the default stacked style: This styling is for a list of links with a description. This is particularly useful for index pages that have an actual list of pages: -Mixed Description List Styles +![Index Description List](/img/IndexDescriptionList_2x.png) ```html