// 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! const isInternalImage = detectInternalImage(requestedImageUrl); if (!isInternalImage) { return requestedImageUrl; } 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 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 : ''; }