Home Manual Reference Source Test Repository

lib/collection/base.js

import Promise from 'bluebird';
import path from 'path';
import moment from 'moment';
import _ from 'lodash';

import CollectionPage from './page';

export default class CollectionBase {
  /**
   * Create a Collection instance.
   * @param {string} name The name of the collection.
   * @param {Object} collectionConfig Config object from config file.
   * @param {Config} config Config instance.
   * @param {Renderer} renderer Renderer instance.
   */
  constructor(name, collectionConfig = {}, config, renderer) {
    if (_.isString(name) === false || name.length === 0) {
      throw new Error('Collection requires a name.');
    }

    /**
     * Unique ID of this Collection, currently its given name.
     * @type {string}
     */
    this.id = name;

    /**
     * The collection name. Must be unique.
     * @type {string}
     */
    this.name = name;

    /**
     * Data accesible to templates.
     * @type {Object}
     */
    this.data = {};

    /**
     * @type {Config}
     * @protected
     */
    this._config = config;

    /**
     * @type {Renderer}
     * @protected
     */
    this._renderer = renderer;

    if (!_.isUndefined(collectionConfig.path)) {
      /**
       * Path where items belong within the collection.
       * @type {string}
       */
      this.path = path.resolve(
        this._config.get('path.source'),
        collectionConfig.path
      );
    }

    if (!_.isUndefined(collectionConfig.metadata)) {
      /**
       * Metadata attribute to use to find which items are within the
       * collection.
       * @type {string}
       */
      this.metadata = collectionConfig.metadata;
    }

    if (!_.isUndefined(collectionConfig.template)) {
      /**
        * What template to use when rendering a CollectionPage.
       * @type {string}
       */
      this.template = collectionConfig.template;
    }

    if (!_.isUndefined(collectionConfig.pageSize)) {
      if (!_.isNumber(collectionConfig.pageSize)) {
        throw new Error('Page size must be a number');
      }

      /**
       * Size of each CollectionPage.
       * @type {number}
       */
      this.pageSize = collectionConfig.pageSize;
    }

    if (!_.isUndefined(collectionConfig.sort)) {
      /**
       * Sorting configuration.
       * @type {Object}
       */
      this.sort = {
        key: collectionConfig.sort.key,
        order: collectionConfig.sort.order,
      };
    }

    if (!_.isUndefined(collectionConfig.permalink)) {
      const permalinkConfig = collectionConfig.permalink;

      /**
       * Permalink information.
       * @type {Object}
       */
      this.permalink = {};

      if (!_.isUndefined(permalinkConfig.index)) {
        /**
         * Permalink index configuration.
         * @type {string}
         */
        this.permalink.index = permalinkConfig.index;
      }

      if (!_.isUndefined(permalinkConfig.page)) {
        /**
         * Permalink page configuration.
         * @type {string}
         */
        this.permalink.page = permalinkConfig.page;
      }
    }

    /**
     * Array of CollectionPage objects.
     * @type {Array.<CollectionPage>}
     */
    this.pages = [];
  }

  /**
   * Whether a File is filtered by the configured filters.
   * @param {File} file File object.
   * @return {boolean} Whether this file should be filtered out.
   */
  // eslint-disable-next-line class-methods-use-this
  isFiltered(file) {
    return file.filtered === true;
  }

  /**
   * Populate the Collection's files via file system path or metadata attribute.
   * @param {Object.<string, Files>} files All Files.
   * @param {Object.<string, CollectionBase>} collections Object of all
   *   collections.
   * @return {CollectionBase}
   */
  // eslint-disable-next-line no-unused-vars
  populate(files = {}, collections = {}) {
    return this;
  }

  /**
   * Create a CollectionPage instance.
   * @param {number} index Index of the page.
   * @param {string?} pageIdArg Optional custom ID for a CollectionPage.
   * @return {CollectionPage} CollectionPage instance.
   */
  createPage(index, pageIdArg) {
    if (!_.isNumber(index)) {
      throw new Error('Must give an index when creating a CollectionPage.');
    }

    const pageId = _.isUndefined(pageIdArg) ? this.id : pageIdArg;

    const page = new CollectionPage(pageId, index, {
      config: this._config,
      renderer: this._renderer,
    });

    page.permalink = index === 0 ? this.permalink.index : this.permalink.page;

    // Give each CollectionPage what template it should use.
    page.data.template = this.template;

    return page;
  }

  _linkPages(shouldLinkPrevious, shouldLinkNext) {
    if (this.pages.length > 0) {
      this.pages.forEach((collectionPage, index) => {
        const previous = this.pages[index - 1];

        if (shouldLinkPrevious(previous, collectionPage)) {
          collectionPage.setPreviousPage(previous);
        }

        const next = this.pages[index + 1];

        if (shouldLinkNext(next, collectionPage)) {
          collectionPage.setNextPage(next);
        }
      });
    }

    // Add data to template accessible object.
    this.data.pages = this.pages.map(page => page.data);

    return this;
  }

  /**
   * Writes every page in this collection.
   * @param {Object} siteData Site wide data that is shared on all rendered
   *  pages.
   * @return {Promise}
   */
  write(siteData) {
    let pagePromises = [];

    // Write CollectionPage files.
    if (this.pages.length) {
      pagePromises = this.pages.map(collectionPage =>
        collectionPage.write(siteData)
      );
    }

    return Promise.all(pagePromises);
  }

  /**
   * Sorts files according to a sort config object.
   * @param {Array.<File>} files Array of File objects.
   * @param {Object} sortConfig Sort config object.
   * @param {string?} dateFormat Optional. File date format from config.
   * @return {Array.<file>} Sorted files.
   */
  static sortFiles(files, sortConfig, dateFormat) {
    const validSortConfig = sortConfig && sortConfig.key;
    const hasFiles = files && files.length > 0;

    if (validSortConfig && hasFiles) {
      // Convert a File's date value to a time stamp for sorting.
      const getFileDateTime = file =>
        moment(file.data[sortConfig.key], dateFormat)
          .toDate()
          .getTime();

      // Quick sniff to see if the sort value is a date object.
      const sortIsDate = getFileDateTime(files[0]) > 0;

      // eslint-disable-next-line no-param-reassign
      files = _.sortBy(
        files,
        sortIsDate ? getFileDateTime : `data[${sortConfig.key}]`
      );

      if (sortConfig.order === 'descending') {
        files.reverse();
      }
    }

    return files;
  }
}