all files / lib/collection/type/ metadata.js

97.62% Statements 82/84
90.91% Branches 30/33
100% Functions 16/16
95.83% Lines 46/48
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204                                       15×                   51×                                                         51× 19×     32× 32×     42× 42× 42×             42×     32×               11×   11×               50×     11×   11×                   14×       11× 11×     33×   33×           34×   34×         34×                                       46× 46×                 11×             34×                 34×         11×      
import isUndefined from 'lodash/isUndefined';
import chunk from 'lodash/chunk';
import each from 'lodash/each';
 
import CollectionBase from '../base';
 
/**
 * A collection that derives its content from a match in a files yaml
 * frontmatter data.
 */
export default class MetadataCollection extends CollectionBase {
  constructor(name, collectionConfig, getConfig) {
    super(name, collectionConfig, getConfig);
 
    /**
     * Object which holds a mapping of metadata value to the files that contain
     * the metadata property.
     * For example with metadata of 'tags' you'd have:
     * {
     * 	'tag-name': [file, file],
     * 	'other-tag': [file, file]
     * }
     * @type {Object.<string, Array.<File>>}
     */
    this.metadataFiles;
  }
 
  /**
   * Checks to see if this file passes all requirements to be considered a part
   * of this collection.
   * @param {File} file File object.
   * @return {boolean} true if the file meets all requirements.
   */
  _isFileInCollection(file) {
    return !isUndefined(file.data[this.metadata]) && !this.isFiltered(file);
  }
 
  /**
   * Removes a file from the collection.
   * @param {File} file File object.
   * @return {boolean} True if the file was removed from the collection.
   */
  removeFile(file) {
    Iif (!this._isFileInCollection(file)) {
      return false;
    }
 
    let metadataValues = file.data[this.metadata];
    Iif (!Array.isArray(metadataValues)) {
      metadataValues = [metadataValues];
    }
 
    metadataValues.forEach(value => {
      // Remove File.
      let fileIndex = this.metadataFiles[value].indexOf(file);
      this.metadataFiles[value].splice(fileIndex, 1);
 
      // Remove data from template accessible object.
      let dataIndex = this.data.metadata[value].indexOf(file.data);
      this.metadataFiles[value].splice(dataIndex, 1);
    });
 
    return true;
  }
 
  /**
   * Add a file to the collection.
   * @param {File} file File object.
   * @return {boolean} True if the file was added to the collection.
   */
  addFile(file) {
    if (!this._isFileInCollection(file)) {
      return false;
    }
 
    let metadataValues = file.data[this.metadata];
    if (!Array.isArray(metadataValues)) {
      metadataValues = [metadataValues];
    }
 
    metadataValues.forEach(value => {
      this.metadataFiles[value] = this.metadataFiles[value] || [];
      this.metadataFiles[value].push(file);
 
      // Add collection names.
      file.collectionIds.add(this.id);
 
      // Add data to template accessible object.
      this.data.metadata[value] = this.data.metadata[value] || [];
      this.data.metadata[value].push(file.data);
    });
 
    return true;
  }
 
  /**
   * Populate the Collection's files via file system path or metadata attribute.
   * @param {Object.<string, Files>} files Object of files.
   * @return {Collection}
   */
  populate(files) {
    // Create metadata files.
    this.metadataFiles = {};
 
    // Initialize template data.
    this.data.metadata = {};
 
    // Store files that are in our collection.
    each(files, file => {
      // Don't return value so we iterate over every file.
      this.addFile(file);
    });
 
    this.createCollectionPages();
 
    return this;
  }
 
  /**
   * Create CollectionPage objects for our Collection.
   * @return {boolean} True if we successfully created CollectionPages.
   * @private
   */
  createCollectionPages() {
    // If no permalink paths are set then we don't render a CollectionPage.
    if (!(this.pagination &&
          this.pagination.permalinkIndex && this.pagination.permalinkPage)) {
      return false;
    }
 
    Eif (this.metadataFiles) {
      this.pages = [];
 
      // Create CollectionPage objects to represent our pagination pages.
      each(this.metadataFiles, (files, metadataKey) => {
        // Sort files.
        files = CollectionBase.sortFiles(files, this.sort);
 
        // Break up our array of files into arrays that match our defined
        // pagination size.
        let pages = chunk(files, this.pagination.size);
 
        pages.forEach((pageFiles, index) => {
          // Create CollectionPage.
          let collectionPage = this.createPage(
            index,
            `${this.id}:${metadataKey}:${index}` // Custom ID.
          );
 
          collectionPage.setData({
            // Extra template information.
            metadata: metadataKey,
 
            // How many pages in the collection.
            total_pages: pages.length,
 
            // Posts displayed per page
            per_page: this.pagination.size,
 
            // Total number of posts
            total: files.length
          });
 
          // Files in the page.
          collectionPage.setFiles(pageFiles);
 
          // Create a map of the metadataKey to its full URL on the file object.
          // Useful when rendering and wanting to link out to the metadata page.
          pageFiles.forEach(file => {
            file.data.metadataUrls = file.data.metadataUrls || {};
            file.data.metadataUrls[metadataKey] = collectionPage.data.url;
          });
 
          // Add to our array of pages.
          this.pages.push(collectionPage);
        });
      });
    }
 
    this._linkPages(
      // ShouldLinkPrevious
      (previous, collectionPage) => {
        // With metadata collections all pages aren't made in the same context.
        // i.e. for a tag metadata collection you'll have 3 pages with metadata
        // value of 'review', and 2 pages of value 'tutorial'. These different
        // metadata values should not be linked.
        return previous && this.metadataFiles &&
          previous.data.metadata === collectionPage.data.metadata;
      },
      // ShouldLinkNext
      (next, collectionPage) => {
        // With metadata collections all pages aren't made in the same context.
        // i.e. for a tag metadata collection you'll have 3 pages with metadata
        // value of 'review', and 2 pages of value 'tutorial'. These different
        // metadata values should not be linked.
        return next && this.metadataFiles &&
          next.data.metadata === collectionPage.data.metadata;
      }
    );
 
    return true;
  }
}