Created
July 3, 2020 16:38
-
-
Save robhawkes/80fd4c5f1b44985f8c209f5844ef1402 to your computer and use it in GitHub Desktop.
Responsive Cloudinary images in Ghost
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Usage: | |
// `{{img_url feature_image}}` | |
// `{{img_url profile_image absolute="true"}}` | |
// Note: | |
// `{{img_url}}` - does not work, argument is required | |
// | |
// Returns the URL for the current object scope i.e. If inside a post scope will return image permalink | |
// `absolute` flag outputs absolute URL, else URL is relative. | |
const url = require('url'); | |
const _ = require('lodash'); | |
const {urlUtils, logging, i18n} = require('../services/proxy'); | |
const STATIC_IMAGE_URL_PREFIX = `${urlUtils.STATIC_IMAGE_URL_PREFIX}`; | |
module.exports = function imgUrl(requestedImageUrl, options) { | |
// CASE: if no url is passed, e.g. `{{img_url}}` we show a warning | |
if (arguments.length < 2) { | |
logging.warn(i18n.t('warnings.helpers.img_url.attrIsRequired')); | |
return; | |
} | |
// CASE: if url is passed, but it is undefined, then the attribute was | |
// an unknown value, e.g. {{img_url feature_img}} and we also show a warning | |
if (requestedImageUrl === undefined) { | |
logging.warn(i18n.t('warnings.helpers.img_url.attrIsRequired')); | |
return; | |
} | |
// CASE: if you pass e.g. cover_image, but it is not set, then requestedImageUrl is null! | |
// in this case we don't show a warning | |
if (requestedImageUrl === null) { | |
return; | |
} | |
// CASE: if you pass an external image, there is nothing we want to do to it! | |
// ROBIN: Customuised this to modify Cloudinary images | |
const isInternalImage = detectInternalImage(requestedImageUrl); | |
if (!isInternalImage) { | |
return getExternalImage(requestedImageUrl, options); | |
} | |
const {requestedSize, imageSizes} = getImageSizeOptions(options); | |
const absoluteUrlRequested = getAbsoluteOption(options); | |
function applyImageSizes(image) { | |
return getImageWithSize(image, requestedSize, imageSizes); | |
} | |
function getImageUrl(image) { | |
return urlUtils.urlFor('image', {image}, absoluteUrlRequested); | |
} | |
function ensureRelativePath(image) { | |
return urlUtils.absoluteToRelative(image); | |
} | |
// CASE: only make paths relative if we didn't get a request for an absolute url | |
const maybeEnsureRelativePath = !absoluteUrlRequested ? ensureRelativePath : _.identity; | |
return maybeEnsureRelativePath( | |
getImageUrl( | |
applyImageSizes(requestedImageUrl) | |
) | |
); | |
}; | |
function getExternalImage(requestedImageUrl, options) { | |
if (detectCloudinaryImage) { | |
return injectCloudinaryParameters(requestedImageUrl, options); | |
} | |
return requestedImageUrl; | |
} | |
function detectCloudinaryImage(requestedImageUrl) { | |
return /cloudinary.com\/YOUR_CLOUDINARY_SITE/.test(requestedImageUrl); | |
} | |
function injectCloudinaryParameters(requestedImageUrl, options) { | |
const parameters = options && options.hash && options.hash.cloudinary; | |
if (!parameters) { | |
return requestedImageUrl; | |
} | |
const defaultParam = 'YOUR_DEFAULT_CLOUDINARY_TRANSFORM'; | |
const newParams = [defaultParam, parameters].join('/'); | |
return requestedImageUrl.replace(defaultParam, newParams); | |
} | |
function getAbsoluteOption(options) { | |
const absoluteOption = options && options.hash && options.hash.absolute; | |
return absoluteOption ? !!absoluteOption && absoluteOption !== 'false' : false; | |
} | |
function getImageSizeOptions(options) { | |
const requestedSize = options && options.hash && options.hash.size; | |
const imageSizes = options && options.data && options.data.config && options.data.config.image_sizes; | |
return { | |
requestedSize, | |
imageSizes | |
}; | |
} | |
function detectInternalImage(requestedImageUrl) { | |
const siteUrl = urlUtils.getSiteUrl(); | |
const isAbsoluteImage = /https?:\/\//.test(requestedImageUrl); | |
const isAbsoluteInternalImage = isAbsoluteImage && requestedImageUrl.startsWith(siteUrl); | |
// CASE: imagePath is a "protocol relative" url e.g. "//www.gravatar.com/ava..." | |
// by resolving the the imagePath relative to the blog url, we can then | |
// detect if the imagePath is external, or internal. | |
const isRelativeInternalImage = !isAbsoluteImage && url.resolve(siteUrl, requestedImageUrl).startsWith(siteUrl); | |
return isAbsoluteInternalImage || isRelativeInternalImage; | |
} | |
function getImageWithSize(imagePath, requestedSize, imageSizes) { | |
const hasLeadingSlash = imagePath[0] === '/'; | |
if (hasLeadingSlash) { | |
return '/' + getImageWithSize(imagePath.slice(1), requestedSize, imageSizes); | |
} | |
if (!requestedSize) { | |
return imagePath; | |
} | |
if (!imageSizes || !imageSizes[requestedSize]) { | |
return imagePath; | |
} | |
const {width, height} = imageSizes[requestedSize]; | |
if (!width && !height) { | |
return imagePath; | |
} | |
const [imgBlogUrl, imageName] = imagePath.split(STATIC_IMAGE_URL_PREFIX); | |
const sizeDirectoryName = prefixIfPresent('w', width) + prefixIfPresent('h', height); | |
return [imgBlogUrl, STATIC_IMAGE_URL_PREFIX, `/size/${sizeDirectoryName}`, imageName].join(''); | |
} | |
function prefixIfPresent(prefix, string) { | |
return string ? prefix + string : ''; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment