medialize/ally.js

View on GitHub
build/metalsmith/plugins/absolute-url.js

Summary

Maintainability
A
1 hr
Test Coverage

'use strict';

/* eslint-disable new-cap */

/*
  This is necessary to allow easy URL creation in templates, while
  maintaining the deployment flexibility of relative URLs, as well
  as allowing the metalsmith link-checker to work properly.

  USAGE:

    provide metalsmith.data.root.websiteRoot for use in templates
    absoluteUrl({ property: "websiteRoot", define: "/hello/world" })

    convert all URLs beginning with websiteRoot to relative URLs
    absoluteUrl({ resolve: "/hello/world", canonical: "https://example.org/" })
*/

const path = require('path');
const cheerio = require('cheerio');
const URI = require('urijs');

const urlSelector = Object.keys(URI.domAttributes).map(function(tagName) {
  return tagName + '[' + URI.domAttributes[tagName] + ']';
}).join(',');

function filter(file, files, filePath /*, options */) {
  // skip mutations for anything that isn't html
  if (filePath.ext !== '.html') {
    return true;
  }

  return false;
}

function transform($, file, fileName, options) {
  const absolute = path.join(options.resolve, fileName);
  if (options.canonical) {
    const canonical = options.canonical + fileName;
    const $canonical = $('<link rel="canonical" href="">').attr('href', canonical);
    $('title').after($canonical);
  }

  $(urlSelector).each(function() {
    const $element = $(this);
    $element.nodeName = $element[0].name;
    const attribute = URI.getDomAttribute($element);
    if (!attribute) {
      // element does not have a URL attribute
      return;
    }

    const url = $element.attr(attribute);
    if (url.slice(0, options.resolve.length) !== options.resolve) {
      // URL is not using the absolute prefix
      return;
    }

    let resolved = URI(url).relativeTo(absolute).toString();
    if (!resolved) {
      // the file is linking to itself
      resolved = URI(fileName).filename();
    }

    if (resolved[resolved.length - 1] === '/') {
      // the file is linking to a directory, append index.html
      resolved += 'index.html';
    }

    $element.attr(attribute, resolved);
  });
}

module.exports = function plugin(options) {
  if (!options) {
    options = {};
  }

  if (options.define) {
    return function(files, metalsmith, done) {
      const metadata = metalsmith.metadata();
      metadata[options.property || 'websiteRoot'] = options.define;
      done();
    };
  }

  return function(files, metalsmith, done) {
    Object.keys(files).forEach(function(key) {
      const filePath = path.parse(key);
      if (filter(key, files, filePath, options)) {
        return;
      }

      const page = cheerio.load(files[key].contents);
      transform(page, files[key], key, options);
      files[key].contents = new Buffer(page.html());
    });

    done();
  };
};