Skip to content

Instantly share code, notes, and snippets.

@Defite
Created July 22, 2021 15:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Defite/7439f77d8269d328a6bf82ff620018d6 to your computer and use it in GitHub Desktop.
Save Defite/7439f77d8269d328a6bf82ff620018d6 to your computer and use it in GitHub Desktop.
Ghost.js, prepend image urls with Cloudinary links. Works for Ghost 4.9.4
{
...
"imageOptimization": {
"cloudinary": {
"baseUrl": "https://res.cloudinary.com/summertimesadness/image/fetch/f_auto,q_auto"
}
}
}
//versions/4.9.4/node_modules/@tryghost/kg-default-cards/lib/cards/image.js
const {
isLocalContentImage,
isUnsplashImage,
getAvailableImageWidths,
setSrcsetAttribute,
resizeImage
} = require('../utils');
const {
absoluteToRelative,
relativeToAbsolute,
htmlAbsoluteToRelative,
htmlRelativeToAbsolute,
htmlToTransformReady,
toTransformReady
} = require('@tryghost/url-utils/lib/utils');
module.exports = {
name: 'image',
type: 'dom',
render({ payload, env: { dom }, options = {} }) {
if (!payload.src) {
return dom.createTextNode('');
}
const figure = dom.createElement('figure');
let figureClass = 'kg-card kg-image-card';
if (payload.cardWidth) {
figureClass = `${figureClass} kg-width-${payload.cardWidth}`;
}
figure.setAttribute('class', figureClass);
const img = dom.createElement('img');
const { cloudinary } = options.imageOptimization;
const payloadSrc = cloudinary ? `${cloudinary.baseUrl}/${payload.src}` : payload.src;
img.setAttribute('src', payloadSrc);
img.setAttribute('class', 'kg-image');
img.setAttribute('alt', payload.alt || '');
img.setAttribute('loading', 'lazy');
if (payload.title) {
img.setAttribute('title', payload.title);
}
if (payload.width && payload.height) {
img.setAttribute('width', payload.width);
img.setAttribute('height', payload.height);
}
// images can be resized to max width, if that's the case output
// the resized width/height attrs to ensure 3rd party gallery plugins
// aren't affected by differing sizes
const { canTransformImage } = options;
const { defaultMaxWidth } = options.imageOptimization || {};
if (
defaultMaxWidth &&
payload.width > defaultMaxWidth &&
isLocalContentImage(payload.src, options.siteUrl) &&
canTransformImage &&
canTransformImage(payload.src)
) {
const { width, height } = resizeImage(payload, { width: defaultMaxWidth });
img.setAttribute('width', width);
img.setAttribute('height', height);
}
// add srcset unless it's an email, email clients do not have good support for srcset or sizes
if (options.target !== 'email') {
setSrcsetAttribute(img, payload, options);
if (img.getAttribute('srcset') && payload.width && payload.width >= 720) {
// standard size
if (!payload.cardWidth) {
img.setAttribute('sizes', '(min-width: 720px) 720px');
}
if (payload.cardWidth === 'wide' && payload.width >= 1200) {
img.setAttribute('sizes', '(min-width: 1200px) 1200px');
}
}
}
// Outlook is unable to properly resize images without a width/height
// so we add that at the expected size in emails (600px) and use a higher
// resolution image to keep images looking good on retina screens
if (options.target === 'email' && payload.width && payload.height) {
let imageDimensions = {
width: payload.width,
height: payload.height
};
if (payload.width >= 600) {
imageDimensions = resizeImage(imageDimensions, { width: 600 });
}
img.setAttribute('width', imageDimensions.width);
img.setAttribute('height', imageDimensions.height);
if (isLocalContentImage(payload.src, options.siteUrl) && options.canTransformImage && options.canTransformImage(payload.src)) {
// find available image size next up from 2x600 so we can use it for the "retina" src
const availableImageWidths = getAvailableImageWidths(payload, options.imageOptimization.contentImageSizes);
const srcWidth = availableImageWidths.find(width => width >= 1200);
if (!srcWidth || srcWidth === payload.width) {
// do nothing, width is smaller than retina or matches the original payload src
} else {
const [, imagesPath, filename] = payload.src.match(/(.*\/content\/images)\/(.*)/);
img.setAttribute('src', `${imagesPath}/size/w${srcWidth}/${filename}`);
}
}
if (isUnsplashImage(payload.src)) {
const unsplashUrl = new URL(payload.src);
unsplashUrl.searchParams.set('w', 1200);
img.setAttribute('src', unsplashUrl.href);
}
}
if (payload.href) {
const a = dom.createElement('a');
a.setAttribute('href', payload.href);
a.appendChild(img);
figure.appendChild(a);
} else {
figure.appendChild(img);
}
if (payload.caption) {
const figcaption = dom.createElement('figcaption');
figcaption.appendChild(dom.createRawHTMLSection(payload.caption));
figure.appendChild(figcaption);
figure.setAttribute('class', `${figure.getAttribute('class')} kg-card-hascaption`);
}
return figure;
},
absoluteToRelative(payload, options) {
payload.src = payload.src && absoluteToRelative(payload.src, options.siteUrl, options);
payload.caption = payload.caption && htmlAbsoluteToRelative(payload.caption, options.siteUrl, options);
return payload;
},
relativeToAbsolute(payload, options) {
payload.src = payload.src && relativeToAbsolute(payload.src, options.siteUrl, options.itemUrl, options);
payload.caption = payload.caption && htmlRelativeToAbsolute(payload.caption, options.siteUrl, options.itemUrl, options);
return payload;
},
toTransformReady(payload, options) {
payload.src = payload.src && toTransformReady(payload.src, options.siteUrl, options);
payload.caption = payload.caption && htmlToTransformReady(payload.caption, options.siteUrl, options);
return payload;
}
};
// versions/4.9.4/node_modules/@tryghost/kg-default-cards/lib/utils/set-srcset-attribute.js
const isLocalContentImage = require('./is-local-content-image');
const getAvailableImageWidths = require('./get-available-image-widths');
const isUnsplashImage = require('./is-unsplash-image');
// default content sizes: [600, 1000, 1600, 2400]
module.exports = function setSrcsetAttribute(elem, image, options) {
if (!elem || !['IMG', 'SOURCE'].includes(elem.tagName) || !elem.getAttribute('src') || !image) {
return;
}
if (!options.imageOptimization || options.imageOptimization.srcsets === false || !image.width || !options.imageOptimization.contentImageSizes) {
return;
}
if (isLocalContentImage(image.src, options.siteUrl) && options.canTransformImage && !options.canTransformImage(image.src)) {
return;
}
const srcsetWidths = getAvailableImageWidths(image, options.imageOptimization.contentImageSizes);
// apply srcset if this is a relative image that matches Ghost's image url structure
if (isLocalContentImage(image.src, options.siteUrl)) {
const [, imagesPath, filename] = image.src.match(/(.*\/content\/images)\/(.*)/);
const srcs = [];
srcsetWidths.forEach((width) => {
if (width === image.width) {
// use original image path if width matches exactly (avoids 302s from size->original)
srcs.push(`${image.src} ${width}w`);
} else if (width <= image.width) {
// avoid creating srcset sizes larger than intrinsic image width
if (options.imageOptimization.cloudinary) {
srcs.push(`${options.imageOptimization.cloudinary.baseUrl},w_${width}/${imagesPath}/${filename} ${width}w`);
} else {
srcs.push(`${imagesPath}/size/w${width}/${filename} ${width}w`);
}
}
});
if (srcs.length) {
elem.setAttribute('srcset', srcs.join(', '));
}
}
// apply srcset if this is an Unsplash image
if (isUnsplashImage(image.src)) {
const unsplashUrl = new URL(image.src);
const srcs = [];
srcsetWidths.forEach((width) => {
unsplashUrl.searchParams.set('w', width);
srcs.push(`${unsplashUrl.href} ${width}w`);
});
elem.setAttribute('srcset', srcs.join(', '));
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment