[<%= include("prev") %>] | [<%= include("next") %>]
14 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var mocha = require('gulp-mocha');
3 | var istanbul = require('gulp-istanbul');
4 | var jshint = require('gulp-jshint');
5 | var del = require('rimraf');
6 | require('jshint-stylish');
7 |
8 | gulp.task('coverage', function () {
9 | return gulp.src(['index.js'])
10 | .pipe(istanbul())
11 | .pipe(istanbul.hookRequire());
12 | });
13 |
14 | gulp.task('coverage:clean', function (cb) {
15 | del('coverage', cb);
16 | });
17 |
18 | gulp.task('mocha', ['coverage'], function () {
19 | return gulp.src('test.js')
20 | .pipe(mocha({reporter: 'spec'}))
21 | .pipe(istanbul.writeReports());
22 | });
23 |
24 | gulp.task('jshint', function () {
25 | return gulp.src(['index.js'])
26 | .pipe(jshint())
27 | .pipe(jshint.reporter('jshint-stylish'))
28 | .pipe(jshint.reporter('fail'));
29 | });
30 |
31 | gulp.task('default', ['mocha', 'jshint']);
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015, Brian Woodward.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.verb.md:
--------------------------------------------------------------------------------
1 | # {%= name %} {%= badge("fury") %} {%= badge("travis") %}
2 |
3 | > {%= description %}
4 |
5 | {%= include("install-npm", {save: true}) %}
6 |
7 | ## Usage
8 |
9 | ```js
10 | var indexer = require('{%= name %}');
11 | ```
12 |
13 | ## API
14 | {%= apidocs("index.js") %}
15 |
16 | ### .addIndices
17 |
18 | `addIndices` method decorated onto the given `collection` Iterators over a list of `pages` (built with `list.paginate`) and adds each page to the collection as a new index view
19 |
20 | **Params**
21 |
22 | * `pages` **{Array}**: Array of pages return from `list.paginate`
23 | * `opts` **{Object}**: Method options to override plugin options. Will also be added to locals for each index view.
24 | * `opts.index` **{Object}**: Optional instance of `View` to use as the basis for the index views being added. Required if `createView` is not passed on plugin or method options.
25 | * `opts.createView` **{Function}**: Function to create a view instance for the index view being added. Required if `index` is not passed on plugin or method options.
26 | * `returns` **{Object}**: Returns `collection` to enable chaining
27 |
28 | **Example**
29 |
30 | ```js
31 | collection.addIndices(pages, locals);
32 | ```
33 |
34 | ## Related projects
35 | {%= related(verb.related.list, {remove: name}) %}
36 |
37 | ## Running tests
38 | {%= include("tests") %}
39 |
40 | ## Contributing
41 | {%= include("contributing") %}
42 |
43 | ## Author
44 | {%= include("author") %}
45 |
46 | ## License
47 | {%= copyright() %}
48 | {%= license() %}
49 |
50 | ***
51 |
52 | {%= include("footer") %}
53 |
54 | {%= reflinks(['templates']) %}
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "assemble-indexer",
3 | "description": "Assemble plugin to add index views to template collections.",
4 | "version": "0.1.2",
5 | "homepage": "https://github.com/assemble/assemble-indexer",
6 | "author": "Brian Woodward (https://github.com/doowb)",
7 | "repository": "assemble/assemble-indexer",
8 | "bugs": {
9 | "url": "https://github.com/assemble/assemble-indexer/issues"
10 | },
11 | "license": "MIT",
12 | "files": [
13 | "index.js"
14 | ],
15 | "main": "index.js",
16 | "engines": {
17 | "node": ">=0.10.0"
18 | },
19 | "scripts": {
20 | "test": "mocha"
21 | },
22 | "dependencies": {
23 | "mixin-deep": "^1.1.3",
24 | "write": "^0.2.1"
25 | },
26 | "devDependencies": {
27 | "ansi-cyan": "^0.1.1",
28 | "ansi-green": "^0.1.1",
29 | "ansi-grey": "^0.1.1",
30 | "ansi-red": "^0.1.1",
31 | "assemble-loader": "^0.1.1",
32 | "assemble-permalinks": "^0.1.0",
33 | "async": "^1.4.2",
34 | "engine-base": "^0.1.2",
35 | "error-symbol": "^0.1.0",
36 | "gulp": "^3.9.0",
37 | "gulp-istanbul": "^0.10.0",
38 | "gulp-jshint": "^1.11.2",
39 | "gulp-mocha": "^2.1.3",
40 | "jshint-stylish": "^2.0.1",
41 | "mocha": "*",
42 | "parser-front-matter": "^1.2.5",
43 | "relative": "^3.0.1",
44 | "rimraf": "^2.4.3",
45 | "should": "^7.1.0",
46 | "success-symbol": "^0.1.0",
47 | "templates": "^0.1.22"
48 | },
49 | "verb": {
50 | "related": {
51 | "list": [
52 | "template",
53 | "templates",
54 | "assemble",
55 | "paginationator"
56 | ]
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * assemble-indexer
3 | *
4 | * Copyright (c) 2015, Brian Woodward.
5 | * Licensed under the MIT License.
6 | */
7 |
8 | 'use strict';
9 | var merge = require('mixin-deep');
10 |
11 | /**
12 | * Add `addIndices` to a [templates][] collection that will
13 | * add index views to the collection when given an array of pages.
14 | *
15 | * ```
16 | * var archives = app.create('archives')
17 | * .use(indexer())
18 | * .addIndices(pages);
19 | * ```
20 | *
21 | * @param {Object} `options`
22 | * @param {Object} `options.index` Optional instance of `View` to use as the basis for the index views being added. Required if `createView` is not passed on plugin or method options.
23 | * @param {Function} `options.createView` Function to create a view instance for the index view being added. Required if `index` is not passed on plugin or method options.
24 | * @return {Function} Function to use as a plugin for [templates][]
25 | * @api public
26 | * @name indexer
27 | */
28 |
29 | module.exports = function indexer (options) {
30 | options = options || {};
31 |
32 | /**
33 | * Plugin passed to [templates][] `.use` method.
34 | *
35 | * @param {Object} `collection` collection instance the plugin is added to.
36 | */
37 |
38 | return function plugin (collection) {
39 | collection.define('addIndices', function (pages, opts) {
40 | opts = merge({}, options, opts);
41 |
42 | var createView = opts.createView;
43 | if (typeof createView !== 'function') {
44 | var index = opts.index;
45 | createView = createViewFn(index);
46 | delete opts.index;
47 | }
48 |
49 | pages.forEach(function (pagination) {
50 | var locals = merge({}, opts);
51 | locals.pages = pages;
52 | locals.pagination = pagination;
53 | var view = createView(locals);
54 | view.key = view.url || view.path;
55 | collection.addView(view);
56 | });
57 | return collection;
58 | });
59 | };
60 | };
61 |
62 | /**
63 | * Default method for creating a new index view.
64 | *
65 | * ```js
66 | * var view = createViewFn(locals);
67 | * ```
68 | *
69 | * @param {Object} `locals` Combined locals for this index view.
70 | * @return {Object} New View instance to be used as the index view
71 | */
72 |
73 | function createViewFn (index) {
74 | if (typeof index !== 'object' || !index.isView) {
75 | throw new Error('expected index to be an instance of View');
76 | }
77 |
78 | return function (locals) {
79 | var view = index.clone({deep: true});
80 | view.locals = merge({}, view.locals, locals);
81 |
82 | if (typeof view.permalink === 'function') {
83 | view.permalink(view.data.permalink, locals);
84 | return view;
85 | }
86 | return view;
87 | };
88 | }
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # assemble-indexer [](http://badge.fury.io/js/assemble-indexer) [](https://travis-ci.org/assemble/assemble-indexer)
2 |
3 | > Assemble plugin to add index views to template collections.
4 |
5 | Install with [npm](https://www.npmjs.com/)
6 |
7 | ```sh
8 | $ npm i assemble-indexer --save
9 | ```
10 |
11 | ## Usage
12 |
13 | ```js
14 | var indexer = require('assemble-indexer');
15 | ```
16 |
17 | ## API
18 |
19 | ### [indexer](index.js#L29)
20 |
21 | Add `addIndices` to a [templates](https://github.com/jonschlinkert/templates) collection that will add index views to the collection when given an array of pages.
22 |
23 | **Params**
24 |
25 | * `options` **{Object}**
26 | * `options.index` **{Object}**: Optional instance of `View` to use as the basis for the index views being added. Required if `createView` is not passed on plugin or method options.
27 | * `options.createView` **{Function}**: Function to create a view instance for the index view being added. Required if `index` is not passed on plugin or method options.
28 | * `returns` **{Function}**: Function to use as a plugin for [templates](https://github.com/jonschlinkert/templates)
29 |
30 | **Example**
31 |
32 | ```
33 | var archives = app.create('archives')
34 | .use(indexer())
35 | .addIndices(pages);
36 | ```
37 |
38 | ### .addIndices
39 |
40 | `addIndices` method decorated onto the given `collection` Iterators over a list of `pages` (built with `list.paginate`) and adds each page to the collection as a new index view
41 |
42 | **Params**
43 |
44 | * `pages` **{Array}**: Array of pages return from `list.paginate`
45 | * `opts` **{Object}**: Method options to override plugin options. Will also be added to locals for each index view.
46 | * `opts.index` **{Object}**: Optional instance of `View` to use as the basis for the index views being added. Required if `createView` is not passed on plugin or method options.
47 | * `opts.createView` **{Function}**: Function to create a view instance for the index view being added. Required if `index` is not passed on plugin or method options.
48 | * `returns` **{Object}**: Returns `collection` to enable chaining
49 |
50 | **Example**
51 |
52 | ```js
53 | collection.addIndices(pages, locals);
54 | ```
55 |
56 | ## Related projects
57 |
58 | * [assemble](https://www.npmjs.com/package/assemble): Static site generator for Grunt.js, Yeoman and Node.js. Used by Zurb Foundation, Zurb Ink, H5BP/Effeckt,… [more](https://www.npmjs.com/package/assemble) | [homepage](http://assemble.io)
59 | * [paginationator](https://www.npmjs.com/package/paginationator): Paginate an array into pages of items. | [homepage](https://github.com/doowb/paginationator)
60 | * [template](https://www.npmjs.com/package/template): Render templates using any engine. Supports, layouts, pages, partials and custom template types. Use template… [more](https://www.npmjs.com/package/template) | [homepage](https://github.com/jonschlinkert/template)
61 | * [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine.… [more](https://www.npmjs.com/package/templates) | [homepage](https://github.com/jonschlinkert/templates)
62 |
63 | ## Running tests
64 |
65 | Install dev dependencies:
66 |
67 | ```sh
68 | $ npm i -d && npm test
69 | ```
70 |
71 | ## Contributing
72 |
73 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/assemble/assemble-indexer/issues/new).
74 |
75 | ## Author
76 |
77 | **Brian Woodward**
78 |
79 | + [github/doowb](https://github.com/doowb)
80 | + [twitter/doowb](http://twitter.com/doowb)
81 |
82 | ## License
83 |
84 | Copyright © 2015 Brian Woodward
85 | Released under the MIT license.
86 |
87 | ***
88 |
89 | _This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on September 20, 2015._
--------------------------------------------------------------------------------
/examples/pagination.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var red = require('ansi-red');
3 | var cyan = require('ansi-cyan');
4 | var grey = require('ansi-grey');
5 | var green = require('ansi-green');
6 | var error = require('error-symbol');
7 | var templates = require('templates');
8 | var success = require('success-symbol');
9 | var matter = require('parser-front-matter');
10 | var permalink = require('assemble-permalinks');
11 | var writeFile = require('write');
12 | var async = require('async');
13 | var List = templates.List;
14 | var app = templates();
15 |
16 | app.engine('txt', require('engine-base'));
17 |
18 | app.onLoad(/\.txt$/, function (view, next) {
19 | matter.parse(view, next);
20 | });
21 |
22 | /**
23 | * Create a collection
24 | */
25 |
26 | app.create('layouts', {viewType: ['layout']});
27 | app.create('posts')
28 | .use(permalink(':base/:name.html'));
29 |
30 | app.create('includes', {viewType: ['partial'], engine: 'txt'});
31 |
32 | app.helper('relative', require('relative'));
33 |
34 | app.layout('default', {
35 | content: [
36 | '',
37 | '',
38 | ' ',
39 | ' ',
40 | ' <%= title %>',
41 | ' ',
42 | ' ',
43 | ' {% body %}',
44 | ' ',
45 | '',
46 | ].join('\n')
47 | });
48 |
49 | app.include('prev', { content: '">Prev' });
50 | app.include('next', { content: '">Next' });
51 |
52 | /**
53 | * Add some posts to the `posts` collection
54 | */
55 | function content (title, body) {
56 | return [
57 | '---',
58 | 'title: ' + title,
59 | '---',
60 | '<%= title %>
',
61 | '' + body + '
',
62 | '[<%= include("prev") %>] | [<%= include("next") %>]'
63 | ].join('\n');
64 | }
65 |
66 | app.posts({
67 | 'a/b/c/a.txt': {
68 | locals: {base: 'pagination/blog'},
69 | content: content('A', 'aaa')
70 | },
71 | 'a/b/c/b.txt': {
72 | locals: {base: 'pagination/blog'},
73 | content: content('B', 'bbb')
74 | },
75 | 'a/b/c/c.txt': {
76 | locals: {base: 'pagination/blog'},
77 | content: content('C', 'ccc')
78 | },
79 | 'a/b/c/d.txt': {
80 | locals: {base: 'pagination/blog'},
81 | content: content('D', 'ddd')
82 | },
83 | 'a/b/c/e.txt': {
84 | locals: {base: 'pagination/blog'},
85 | content: content('E', 'eee')
86 | },
87 | 'a/b/c/f.txt': {
88 | locals: {base: 'pagination/blog'},
89 | content: content('F', 'fff')
90 | },
91 | 'a/b/c/g.txt': {
92 | locals: {base: 'pagination/blog'},
93 | content: content('G', 'ggg')
94 | },
95 | 'a/b/c/h.txt': {
96 | locals: {base: 'pagination/blog'},
97 | content: content('H', 'hhh')
98 | },
99 | 'a/b/c/i.txt': {
100 | locals: {base: 'pagination/blog'},
101 | content: content('I', 'iii')
102 | },
103 | 'a/b/c/j.txt': {
104 | locals: {base: 'pagination/blog'},
105 | content: content('J', 'jjj')
106 | },
107 | });
108 |
109 | var list = new List(app.posts)
110 | var pagination = list.paginate({limit: 3})
111 |
112 | async.eachSeries(list.items, function (post, next) {
113 | post.permalink(post.data.permalink, post.locals);
114 | process.stdout.write(cyan('Rendering ') + grey(post.url) + cyan(' => '));
115 | post.render({layout: 'default'}, function (err, res) {
116 | if (err) return next(err);
117 | var dest = path.join(__dirname, '../actual', post.url);
118 | process.stdout.write(grey(path.relative(process.cwd(), dest)) + '... ');
119 | writeFile(dest, post.content, function (err) {
120 | if (err) {
121 | process.stdout.write(red(error) + '\n');
122 | return next(err);
123 | }
124 | process.stdout.write(green(success) + '\n');
125 | next();
126 | });
127 | });
128 | }, function (err) {
129 | if (err) return console.error(err);
130 | console.log('done');
131 | });
132 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | require('should');
5 | var permalink = require('assemble-permalinks');
6 | var templates = require('templates');
7 | var assert = require('assert');
8 | var fs = require('fs');
9 |
10 | var indexer = require('./');
11 |
12 | var List = templates.List;
13 | var app, index;
14 |
15 | describe('indexer', function () {
16 | beforeEach(function () {
17 | app = templates();
18 | app.initialize();
19 | index = app.view({path: 'index.hbs', content: ''})
20 | .use(permalink(':index(pagination.idx):name.html', {
21 | index: function (i) {
22 | return i ? ((i + 1) + '/') : '';
23 | }
24 | }));
25 | });
26 |
27 | it('should throw an error when an index instance is not passed', function () {
28 | var list = new List();
29 | list.addList([
30 | {path: 'a.hbs', content: 'aaa'},
31 | {path: 'b.hbs', content: 'bbb'},
32 | {path: 'c.hbs', content: 'ccc'},
33 | {path: 'd.hbs', content: 'ddd'},
34 | {path: 'e.hbs', content: 'eee'},
35 | ]);
36 | var pages = list.paginate({limit: 2});
37 |
38 | (function () {
39 | app.create('archives')
40 | .use(indexer())
41 | .addIndices(pages);
42 | }).should.throw('expected index to be an instance of View');
43 | });
44 |
45 | it('should add `addIndices` to a templates collection', function () {
46 | app.create('archives')
47 | .use(indexer({index: index}));
48 | app.archives.should.have.property('addIndices');
49 | });
50 |
51 | it('should create index views with default options', function () {
52 | var list = new List();
53 | list.addList([
54 | {path: 'a.hbs', content: 'aaa'},
55 | {path: 'b.hbs', content: 'bbb'},
56 | {path: 'c.hbs', content: 'ccc'},
57 | {path: 'd.hbs', content: 'ddd'},
58 | {path: 'e.hbs', content: 'eee'},
59 | ]);
60 | var pages = list.paginate({limit: 2});
61 |
62 | app.create('archives')
63 | .use(indexer({index: index}))
64 | .addIndices(pages);
65 |
66 | var keys = Object.keys(app.views.archives);
67 | keys.length.should.equal(pages.length);
68 | pages.forEach(function (page) {
69 | var key = (page.isFirst ? '' : page.current + '/') + 'index.html';
70 | assert.equal(keys.indexOf(key) === -1, false);
71 | assert.deepEqual(app.views.archives[key].locals.pagination, page);
72 | });
73 | });
74 |
75 | it('should create index views with default options when permalink is not installed', function () {
76 | var i = 0;
77 | var index = {
78 | isView: true,
79 | path: 'index.hbs',
80 | content: 'index',
81 | clone: function () {
82 | var obj = {
83 | path: (i === 0 ? '' : (i + 1) + '/') + 'index.html',
84 | content: 'index'
85 | };
86 | i++;
87 | return obj;
88 | }
89 | };
90 |
91 | var list = new List();
92 | list.addList([
93 | {path: 'a.hbs', content: 'aaa'},
94 | {path: 'b.hbs', content: 'bbb'},
95 | {path: 'c.hbs', content: 'ccc'},
96 | {path: 'd.hbs', content: 'ddd'},
97 | {path: 'e.hbs', content: 'eee'},
98 | ]);
99 | var pages = list.paginate({limit: 2});
100 |
101 | app.create('archives')
102 | .use(indexer({index: index}))
103 | .addIndices(pages);
104 |
105 | var keys = Object.keys(app.views.archives);
106 | keys.length.should.equal(pages.length);
107 | pages.forEach(function (page) {
108 | var key = (page.isFirst ? '' : page.current + '/') + 'index.html';
109 | assert.equal(keys.indexOf(key) === -1, false);
110 | assert.deepEqual(app.views.archives[key].locals.pagination, page);
111 | });
112 | });
113 |
114 | it('should create index views with custom createView function on plugin options', function () {
115 | var contents = fs.readFileSync('fixtures/templates/indices/archive-index.txt');
116 | var archiveIndexView = app.view({path: 'archive-index.hbs', contents: contents})
117 | .use(permalink(':index(pagination.idx):name.html', {
118 | index: function (i) {
119 | return i ? ((i + 1) + '/') : '';
120 | }
121 | }));
122 |
123 | var list = new List();
124 | list.addList([
125 | {path: 'a.hbs', content: 'aaa'},
126 | {path: 'b.hbs', content: 'bbb'},
127 | {path: 'c.hbs', content: 'ccc'},
128 | {path: 'd.hbs', content: 'ddd'},
129 | {path: 'e.hbs', content: 'eee'},
130 | ]);
131 | var pages = list.paginate({limit: 2});
132 |
133 | app.create('archives')
134 | .use(indexer({
135 | createView: function (locals) {
136 | var view = archiveIndexView.clone();
137 | view.locals = locals;
138 | view.permalink(view.data.permalink, locals);
139 | return view;
140 | }
141 | }))
142 | .addIndices(pages);
143 |
144 | var keys = Object.keys(app.views.archives);
145 | keys.length.should.equal(pages.length);
146 | pages.forEach(function (page) {
147 | var key = (page.isFirst ? '' : page.current + '/') + 'archive-index.html';
148 | assert.equal(keys.indexOf(key) === -1, false);
149 | assert.deepEqual(app.views.archives[key].locals.pagination, page);
150 | assert.deepEqual(app.views.archives[key].contents, contents);
151 | });
152 | });
153 |
154 | it('should create index views with custom createView function on method options', function () {
155 | var contents = fs.readFileSync('fixtures/templates/indices/archive-index.txt');
156 | var archiveIndexView = app.view({path: 'archive-index.hbs', contents: contents})
157 | .use(permalink(':index(pagination.idx):name.html', {
158 | index: function (i) {
159 | return i ? ((i + 1) + '/') : '';
160 | }
161 | }));
162 |
163 | var list = new List();
164 | list.addList([
165 | {path: 'a.hbs', content: 'aaa'},
166 | {path: 'b.hbs', content: 'bbb'},
167 | {path: 'c.hbs', content: 'ccc'},
168 | {path: 'd.hbs', content: 'ddd'},
169 | {path: 'e.hbs', content: 'eee'},
170 | ]);
171 | var pages = list.paginate({limit: 2});
172 |
173 | app.create('archives')
174 | .use(indexer())
175 | .addIndices(pages, {
176 | createView: function (locals) {
177 | var view = archiveIndexView.clone();
178 | view.locals = locals;
179 | view.permalink(view.data.permalink, locals);
180 | return view;
181 | }
182 | });
183 |
184 | var keys = Object.keys(app.views.archives);
185 | keys.length.should.equal(pages.length);
186 | pages.forEach(function (page) {
187 | var key = (page.isFirst ? '' : page.current + '/') + 'archive-index.html';
188 | assert.equal(keys.indexOf(key) === -1, false);
189 | assert.deepEqual(app.views.archives[key].locals.pagination, page);
190 | assert.deepEqual(app.views.archives[key].contents, contents);
191 | });
192 | });
193 |
194 | it('should create index views with additional locals', function () {
195 | var contents = fs.readFileSync('fixtures/templates/indices/archive-index.txt');
196 | var archiveIndexView = app.view({path: 'archive-index.hbs', contents: contents})
197 | .use(permalink(':index(pagination.idx):name.html', {
198 | index: function (i) {
199 | return i ? ((i + 1) + '/') : '';
200 | }
201 | }));
202 |
203 | var list = new List();
204 | list.addList([
205 | {path: 'a.hbs', content: 'aaa'},
206 | {path: 'b.hbs', content: 'bbb'},
207 | {path: 'c.hbs', content: 'ccc'},
208 | {path: 'd.hbs', content: 'ddd'},
209 | {path: 'e.hbs', content: 'eee'},
210 | ]);
211 | var pages = list.paginate({limit: 2});
212 |
213 | app.create('archives')
214 | .use(indexer({index: archiveIndexView}))
215 | .addIndices(pages, {title: 'Archives'});
216 |
217 | var keys = Object.keys(app.views.archives);
218 | keys.length.should.equal(pages.length);
219 | pages.forEach(function (page) {
220 | var key = (page.isFirst ? '' : page.current + '/') + 'archive-index.html';
221 | assert.equal(keys.indexOf(key) === -1, false);
222 | assert.deepEqual(app.views.archives[key].locals.title, 'Archives');
223 | assert.deepEqual(app.views.archives[key].locals.pagination, page);
224 | assert.deepEqual(app.views.archives[key].contents, contents);
225 | });
226 | });
227 | });
228 |
--------------------------------------------------------------------------------
/examples/archives.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var async = require('async');
3 | var red = require('ansi-red');
4 | var cyan = require('ansi-cyan');
5 | var grey = require('ansi-grey');
6 | var writeFile = require('write');
7 | var green = require('ansi-green');
8 | var error = require('error-symbol');
9 | var templates = require('templates');
10 | var success = require('success-symbol');
11 | var loader = require('assemble-loader');
12 | var matter = require('parser-front-matter');
13 | var permalink = require('assemble-permalinks');
14 | var indexer = require('../');
15 |
16 | var List = templates.List;
17 | var app = templates()
18 | .use(loader());
19 |
20 | app.engine('txt', require('engine-base'));
21 |
22 | app.option('view engine', 'txt');
23 |
24 | app.option('renameKey', function (fp) {
25 | return path.basename(fp, path.extname(fp));
26 | });
27 | app.helper('relative', require('relative'));
28 |
29 | app.data({base: 'archives/blog'});
30 | app.data({archiveBase: 'archives/blog/archives'});
31 |
32 | app.onLoad(/\.(txt|md)$/, function (view, next) {
33 | matter.parse(view, next);
34 | });
35 |
36 | app.create('layouts', {viewType: ['layout']})
37 | .load(path.join(__dirname, '../fixtures/templates/layouts/*.txt'));
38 |
39 | app.create('includes', {viewType: ['partial'], engine: 'txt'})
40 | .load(path.join(__dirname, '../fixtures/templates/includes/*.txt'));
41 |
42 | /**
43 | * Create a collection
44 | */
45 |
46 | app.create('post')
47 | .use(permalink(':base/:title.html'));
48 |
49 | /**
50 | * Archive posts into a simple list of posts
51 | */
52 |
53 | app.create('archives')
54 | .option('renameKey', renameKey)
55 | .use(indexer());
56 |
57 | /**
58 | * Archive posts into lists based on years
59 | */
60 |
61 | app.create('yearArchives')
62 | .option('renameKey', renameKey)
63 | .use(indexer());
64 |
65 | /**
66 | * Archive posts into lists based on year-months
67 | */
68 |
69 | app.create('monthArchives')
70 | .option('renameKey', renameKey)
71 | .use(indexer());
72 |
73 |
74 | /**
75 | * Load a collection of list index templates
76 | */
77 |
78 | var indices = app.load(path.join(__dirname, '../fixtures/templates/indices/*.txt'));
79 |
80 | /**
81 | * Get the post list index template (view) to use
82 | */
83 |
84 | var posts = indices.getView('posts')
85 | .use(permalink(':base(0)/:base(1)/:index(pagination.idx)/index.html', {
86 | base: function (idx) {
87 | var res = app.cache.data.base;
88 | return res.split('/')[idx];
89 | },
90 | index: function (i) {
91 | return i ? ((i + 1)) : '';
92 | }
93 | }));
94 |
95 | /**
96 | * Get the archive list index template (view) to use
97 | */
98 |
99 | var archives = indices.getView('archive-index')
100 | .use(permalink(':base(0)/:base(1)/:base(2)/:slug/:index(pagination.idx)/index.html', {
101 | base: function (idx) {
102 | var res = app.cache.data.archiveBase;
103 | return res.split('/')[idx];
104 | },
105 | index: function (i) {
106 | return i ? ((i + 1)) : '';
107 | }
108 | }));
109 |
110 | /**
111 | * Add some posts to the `posts` collection
112 | */
113 |
114 | app.posts({
115 | 'a/b/c/a.txt': {
116 | locals: {base: 'archives/blog', year: '2012', month: '01'},
117 | content: '---\ntitle: A\n---\naaa'
118 | },
119 | 'a/b/c/b.txt': {
120 | locals: {base: 'archives/blog', year: '2011', month: '11'},
121 | content: '---\ntitle: B\n---\nbbb'
122 | },
123 | 'a/b/c/c.txt': {
124 | locals: {base: 'archives/blog', year: '2010', month: '04'},
125 | content: '---\ntitle: C\n---\nccc'
126 | },
127 | 'a/b/c/d.txt': {
128 | locals: {base: 'archives/blog', year: '2010', month: '06'},
129 | content: '---\ntitle: D\n---\nddd'
130 | },
131 | 'a/b/c/e.txt': {
132 | locals: {base: 'archives/blog', year: '2011', month: '08'},
133 | content: '---\ntitle: E\n---\neee'
134 | },
135 | 'a/b/c/f.txt': {
136 | locals: {base: 'archives/blog', year: '2013', month: '04'},
137 | content: '---\ntitle: F\n---\nfff'
138 | },
139 | 'a/b/c/g.txt': {
140 | locals: {base: 'archives/blog', year: '2015', month: '12'},
141 | content: '---\ntitle: G\n---\nggg'
142 | },
143 | 'a/b/c/h.txt': {
144 | locals: {base: 'archives/blog', year: '2010', month: '04'},
145 | content: '---\ntitle: H\n---\nhhh'
146 | },
147 | 'a/b/c/i.txt': {
148 | locals: {base: 'archives/blog', year: '2015', month: '01'},
149 | content: '---\ntitle: I\n---\niii'
150 | },
151 | 'a/b/c/j.txt': {
152 | locals: {base: 'archives/blog', year: '2012', month: '03'},
153 | content: '---\ntitle: J\n---\njjj'
154 | },
155 | });
156 |
157 | /**
158 | * Generate a list from the `posts` collection
159 | */
160 |
161 | var list = new List(app.posts);
162 |
163 | /**
164 | * Create the permalink for each post in the `posts` list.
165 | */
166 |
167 | list.items.forEach(function (post) {
168 | post.permalink(post.data.permalink, {title: post.data.title, base: post.locals.base});
169 | });
170 |
171 | /**
172 | * Paginate the `posts` list into pages containing 3 posts on each page.
173 | */
174 |
175 | var pages = list.paginate({limit: 3});
176 |
177 | /**
178 | * Add the paginated pages to the `archives` collection with `addIndices` using the `posts` list index template (view)
179 | */
180 |
181 | app.archives.addIndices(pages, {index: posts});
182 |
183 | /**
184 | * Group `posts` list based on year.
185 | * //=> { '2010':
186 | * //=> [ >,
187 | * //=> >,
188 | * //=> > ],
189 | * //=> '2011':
190 | * //=> [ >,
191 | * //=> > ],
192 | * //=> '2012':
193 | * //=> [ >,
194 | * //=> > ],
195 | * //=> '2013': [ > ],
196 | * //=> '2015':
197 | * //=> [ >,
198 | * //=> > ] }
199 | */
200 |
201 | var yearsGroup = list.groupBy('locals.year');
202 |
203 | /**
204 | * Group `posts` list based on year and month.
205 | * //=> { '2010':
206 | * //=> { '04':
207 | * //=> [ >,
208 | * //=> > ],
209 | * //=> '06': [ > ] },
210 | * //=> '2011':
211 | * //=> { '11': [ > ],
212 | * //=> '08': [ > ] },
213 | * //=> '2012':
214 | * //=> { '01': [ > ],
215 | * //=> '03': [ > ] },
216 | * //=> '2013': { '04': [ > ] },
217 | * //=> '2015':
218 | * //=> { '12': [ > ],
219 | * //=> '01': [ > ] } }
220 | */
221 |
222 | var monthsGroup = list.groupBy('locals.year', 'locals.month');
223 |
224 | /**
225 | * Iterate over the year/month groups that were generated and add
226 | * them to their archive collections
227 | */
228 |
229 | var years = Object.keys(yearsGroup);
230 | years.forEach(function (year) {
231 | var yearGroup = yearsGroup.get(year);
232 | app.yearArchives.addIndices(yearGroup.paginate({limit: 3}), {slug: year, year: year, index: archives});
233 | });
234 |
235 | years = Object.keys(monthsGroup);
236 | years.forEach(function (year) {
237 | var yearGroup = monthsGroup.get(year);
238 | var months = Object.keys(yearGroup);
239 | months.forEach(function (month) {
240 | var monthGroup = monthsGroup.get([year, month].join('.'));
241 | app.monthArchives.addIndices(monthGroup.paginate({limit: 3}), {slug: path.join(year, month), year: year, month: month, index: archives});
242 | });
243 | });
244 |
245 | async.series([
246 | /**
247 | * Render and write out all the `posts` from the `posts` list. They'll be written based on their permalink url.
248 | * These are also sorted by their year and month properties to show how the next/prev links work.
249 | */
250 | renderList.bind(null, new List(list).sortBy('locals.year', 'locals.month'), {layout: 'post'}),
251 | /**
252 | * Render and write out all of the `archives` list pages of `posts` the their permalink generated url.
253 | */
254 | renderList.bind(null, new List(app.archives)),
255 | /**
256 | * Render and write out all of the `year archives` list pages of `posts` the their permalink generated url.
257 | */
258 | renderList.bind(null, new List(app.yearArchives).sortBy('url')),
259 | /**
260 | * Render and write out all of the `month archives` list pages of `posts` the their permalink generated url.
261 | */
262 | renderList.bind(null, new List(app.monthArchives).sortBy('url')),
263 |
264 | ], function (err) {
265 | if (err) return console.error(err);
266 | console.log('Finished');
267 | });
268 |
269 | /**
270 | * Helper function to iterate over a list of templates, render them
271 | * and write them to their generated permalink url
272 | */
273 |
274 | function renderList (list, locals, cb) {
275 | if (typeof locals === 'function') {
276 | cb = locals;
277 | locals = {};
278 | }
279 |
280 | async.eachSeries(list.items, function (item, next) {
281 | process.stdout.write(cyan('Rendering ') + grey(item.url) + cyan(' => '));
282 | item.render(locals, function (err, res) {
283 | if (err) return next(err);
284 | var dest = path.join(__dirname, '../actual', res.url);
285 | process.stdout.write(grey(path.relative(process.cwd(), dest)) + '... ');
286 | writeFile(dest, res.content, function (err) {
287 | if (err) {
288 | process.stdout.write(red(error) + '\n');
289 | return next(err);
290 | }
291 | process.stdout.write(green(success) + '\n');
292 | next();
293 | });
294 | });
295 | }, cb);
296 | }
297 |
298 | function renameKey (fp) {
299 | return fp;
300 | }
301 |
--------------------------------------------------------------------------------