Skip to content

Instantly share code, notes, and snippets.

@Defite
Created July 22, 2021 15:41
Show Gist options
  • Select an option

  • Save Defite/7439f77d8269d328a6bf82ff620018d6 to your computer and use it in GitHub Desktop.

Select an option

Save Defite/7439f77d8269d328a6bf82ff620018d6 to your computer and use it in GitHub Desktop.

Revisions

  1. Defite created this gist Jul 22, 2021.
    8 changes: 8 additions & 0 deletions config.development.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    {
    ...
    "imageOptimization": {
    "cloudinary": {
    "baseUrl": "https://res.cloudinary.com/summertimesadness/image/fetch/f_auto,q_auto"
    }
    }
    }
    155 changes: 155 additions & 0 deletions image.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,155 @@
    //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;
    }
    };
    60 changes: 60 additions & 0 deletions set-srcset-attribute.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,60 @@
    // 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(', '));
    }

    };