├── .gitattributes ├── .travis.yml ├── .editorconfig ├── .gitignore ├── test ├── fixtures │ ├── bar.md │ ├── baz.md │ ├── fez.md │ ├── foo.md │ ├── qux.md │ └── zzz.md └── test.js ├── LICENSE ├── lib └── helpers.js ├── package.json ├── index.js ├── .github └── contributing.md ├── .eslintrc.json ├── .verb.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text eol=lf 3 | 4 | # binaries 5 | *.ai binary 6 | *.psd binary 7 | *.jpg binary 8 | *.gif binary 9 | *.png binary 10 | *.jpeg binary 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | os: 3 | - linux 4 | - osx 5 | language: node_js 6 | node_js: 7 | - node 8 | - '6' 9 | - '4' 10 | matrix: 11 | allow_failures: 12 | - node_js: '4' 13 | - node_js: '0.12' 14 | - node_js: '0.10' 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{**/{actual,fixtures,expected,templates}/**,*.md}] 12 | trim_trailing_whitespace = false 13 | insert_final_newline = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # always ignore files 2 | *.DS_Store 3 | *.sublime-* 4 | 5 | # test related, or directories generated by tests 6 | test/actual 7 | actual 8 | coverage 9 | .nyc* 10 | 11 | # npm 12 | node_modules 13 | npm-debug.log 14 | 15 | # yarn 16 | yarn.lock 17 | yarn-error.log 18 | 19 | # misc 20 | _gh_pages 21 | _draft 22 | _drafts 23 | bower_components 24 | vendor 25 | temp 26 | tmp 27 | TODO.md 28 | -------------------------------------------------------------------------------- /test/fixtures/bar.md: -------------------------------------------------------------------------------- 1 | {{#pager}} 2 | 3 | Prev 4 | Next 5 | {{/pager}} 6 | 7 | 8 | Prev 9 | Next 10 | 11 | 12 | Prev 13 | Next 14 | 15 | 16 | {{> prev }} 17 | {{> next }} 18 | 19 | 20 | 21 | {{> pager }} 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/baz.md: -------------------------------------------------------------------------------- 1 | {{#pager}} 2 | 3 | Prev 4 | Next 5 | {{/pager}} 6 | 7 | 8 | Prev 9 | Next 10 | 11 | 12 | Prev 13 | Next 14 | 15 | 16 | {{> prev }} 17 | {{> next }} 18 | 19 | 20 | 21 | {{> pager }} 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/fez.md: -------------------------------------------------------------------------------- 1 | {{#pager}} 2 | 3 | Prev 4 | Next 5 | {{/pager}} 6 | 7 | 8 | Prev 9 | Next 10 | 11 | 12 | Prev 13 | Next 14 | 15 | 16 | {{> prev }} 17 | {{> next }} 18 | 19 | 20 | 21 | {{> pager }} 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/foo.md: -------------------------------------------------------------------------------- 1 | {{#pager}} 2 | 3 | Prev 4 | Next 5 | {{/pager}} 6 | 7 | 8 | Prev 9 | Next 10 | 11 | 12 | Prev 13 | Next 14 | 15 | 16 | {{> prev }} 17 | {{> next }} 18 | 19 | 20 | 21 | {{> pager }} 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/qux.md: -------------------------------------------------------------------------------- 1 | {{#pager}} 2 | 3 | Prev 4 | Next 5 | {{/pager}} 6 | 7 | 8 | Prev 9 | Next 10 | 11 | 12 | Prev 13 | Next 14 | 15 | 16 | {{> prev }} 17 | {{> next }} 18 | 19 | 20 | 21 | {{> pager }} 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/zzz.md: -------------------------------------------------------------------------------- 1 | {{#pager}} 2 | 3 | Prev 4 | Next 5 | {{/pager}} 6 | 7 | 8 | Prev 9 | Next 10 | 11 | 12 | Prev 13 | Next 14 | 15 | 16 | {{> prev }} 17 | {{> next }} 18 | 19 | 20 | 21 | {{> pager }} 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jon Schlinkert 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var relative = require('relative'); 4 | var helpers = module.exports; 5 | 6 | function relativePath(from, to) { 7 | return relative(from.data.path, to.data.path); 8 | } 9 | 10 | /** 11 | * Stringify attributes on the options `hash`. 12 | * 13 | * @param {Object} `options` 14 | * @return {String} 15 | * @api public 16 | */ 17 | 18 | helpers.attr = function(options) { 19 | return ' ' + Object.keys(options.hash).map(function(key) { 20 | return key + '="' + options.hash[key] + '"'; 21 | }).join(' '); 22 | }; 23 | 24 | helpers.relative = function(from, to) { 25 | return relativePath(from, to); 26 | }; 27 | 28 | helpers.ifFirst = function(pager, str) { 29 | if (!pager.isPager) { 30 | return this.context.pager.isFirst ? pager : ''; 31 | } 32 | return pager.isFirst ? str : ''; 33 | }; 34 | 35 | helpers.ifLast = function(pager, str) { 36 | if (!pager.isPager) { 37 | return this.context.pager.isLast ? pager : ''; 38 | } 39 | return pager.isLast ? str : ''; 40 | }; 41 | 42 | helpers.next = function(pager) { 43 | // if defined as `{{next}}` inside a `{{#pager}}` block 44 | if (!pager.isPager) pager = this.context.pager; 45 | // or, if defined as `{{next pager}}` 46 | return pager.next ? relativePath(pager.current, pager.next) : '#'; 47 | }; 48 | 49 | helpers.prev = function(pager) { 50 | // if defined as `{{prev}}` inside a `{{#pager}}` block 51 | if (!pager.isPager) pager = this.context.pager; 52 | // or, if defined as `{{prev pager}}` 53 | return pager.prev ? relativePath(pager.current, pager.prev) : '#'; 54 | }; 55 | 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assemble-pager", 3 | "description": "Assemble plugin that adds prev/next pager information to the context.", 4 | "version": "0.1.2", 5 | "homepage": "https://github.com/assemble/assemble-pager", 6 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)", 7 | "repository": "assemble/assemble-pager", 8 | "bugs": { 9 | "url": "https://github.com/assemble/assemble-pager/issues" 10 | }, 11 | "license": "MIT", 12 | "files": [ 13 | "index.js", 14 | "lib" 15 | ], 16 | "main": "index.js", 17 | "engines": { 18 | "node": ">=4" 19 | }, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "dependencies": { 24 | "extend-shallow": "^2.0.1", 25 | "is-valid-app": "^0.2.1", 26 | "plugin-error": "^0.1.2", 27 | "relative": "^3.0.2", 28 | "through2": "^2.0.3" 29 | }, 30 | "devDependencies": { 31 | "assemble": "^0.22.0", 32 | "assemble-fs": "^1.0.0", 33 | "gulp-format-md": "^0.1.11", 34 | "mocha": "^3.2.0" 35 | }, 36 | "keywords": [ 37 | "assemble", 38 | "assembleplugin", 39 | "boilerplate", 40 | "build", 41 | "cli", 42 | "cli-app", 43 | "command-line", 44 | "create", 45 | "dev", 46 | "development", 47 | "framework", 48 | "front", 49 | "frontend", 50 | "pager", 51 | "paginate", 52 | "pagination", 53 | "paging", 54 | "plugin", 55 | "project", 56 | "projects", 57 | "scaffold", 58 | "scaffolder", 59 | "scaffolding", 60 | "template", 61 | "templates", 62 | "webapp", 63 | "yeoman", 64 | "yo" 65 | ], 66 | "verb": { 67 | "toc": true, 68 | "layout": "default", 69 | "tasks": [ 70 | "readme" 71 | ], 72 | "plugins": [ 73 | "gulp-format-md" 74 | ], 75 | "lint": { 76 | "reflinks": true 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isValid = require('is-valid-app'); 4 | var extend = require('extend-shallow'); 5 | var PluginError = require('plugin-error'); 6 | var through = require('through2'); 7 | 8 | /** 9 | * Add `prev`/`next` paging information to the context for files 10 | * in the current stream. 11 | * 12 | * @name plugin 13 | * @param {Object} [options] 14 | * @param {String|Array} [options.sortBy] 15 | * @return {Stream} Returns a [vinyl][] stream 16 | * @api public 17 | */ 18 | 19 | module.exports = function(config) { 20 | return function(app) { 21 | if (!isValid(app, 'assemble-pager')) return; 22 | 23 | /** 24 | * Add `prev`/`next` paging information to the context for files 25 | * in the current stream. 26 | * 27 | * @name .pager 28 | * @param {Object} [options] 29 | * @param {String|Array} [options.sortBy] 30 | * @return {Stream} Returns a [vinyl][] stream 31 | * @api public 32 | */ 33 | 34 | app.define('pager', function(options) { 35 | var opts = extend({}, config, options); 36 | var list = new app.List({pager: true}); 37 | 38 | return through.obj(function(file, enc, next) { 39 | if (file.isNull()) { 40 | next(null, file); 41 | return; 42 | } 43 | 44 | if (!file.isView) { 45 | next(new PluginError('assemble-pager', 'expected an assemble view', {showStack: true})); 46 | return; 47 | } 48 | 49 | if (file.data.pager === false) { 50 | next(null, file); 51 | return; 52 | } 53 | 54 | if (opts.collection && file.options.collection !== opts.collection) { 55 | next(null, file); 56 | return; 57 | } 58 | 59 | list.addItem(file); 60 | next(); 61 | }, function(cb) { 62 | if (opts.sortBy) { 63 | list = list.sortBy(opts.sortBy); 64 | } 65 | 66 | for (var i = 0; i < list.items.length; i++) { 67 | this.push(list.items[i]); 68 | } 69 | cb(); 70 | }); 71 | }); 72 | }; 73 | }; 74 | 75 | /** 76 | * Expose helpers 77 | */ 78 | 79 | module.exports.helpers = require('./lib/helpers'); 80 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | var utils = require('assemble-fs/utils'); 5 | var assemble = require('assemble'); 6 | var assert = require('assert'); 7 | var pager = require('..'); 8 | var app; 9 | 10 | var partials = { 11 | prev: `Prev`, 12 | next: `\nNext`, 13 | pager: '{{> prev }}\n{{> next }}' 14 | }; 15 | 16 | describe('assemble-pager', function() { 17 | beforeEach(function() { 18 | app = assemble(); 19 | app.use(pager()); 20 | app.partials(partials); 21 | 22 | app.onLoad(/\.(md|hbs)$/, function(view, next) { 23 | view.extname = '.html'; 24 | next(); 25 | }); 26 | }); 27 | 28 | it('should load helpers', function() { 29 | app.helpers(pager.helpers); 30 | assert(app._.helpers.sync.relative); 31 | assert(app._.helpers.sync.next); 32 | assert(app._.helpers.sync.prev); 33 | assert(app._.helpers.sync.ifFirst); 34 | assert(app._.helpers.sync.ifLast); 35 | }); 36 | 37 | it('should render pager info for each item in a collection', function(cb) { 38 | app = assemble(); 39 | app.use(pager()); 40 | app.partials(partials); 41 | app.helpers(pager.helpers); 42 | 43 | utils.prepareDest(app, 'foo', {}); 44 | 45 | app.onLoad(/\.(md|hbs)$/, function(view, next) { 46 | view.extname = '.html'; 47 | next(); 48 | }); 49 | 50 | var files = []; 51 | 52 | var stream = app.src('fixtures/*.md', {cwd: __dirname}) 53 | .pipe(app.pager()) 54 | .pipe(app.renderFile()) 55 | .on('data', function(file) { 56 | assert(file.data.pager); 57 | assert.equal(typeof file.data.pager.index, 'number'); 58 | files.push(file); 59 | }) 60 | .on('end', function() { 61 | files.forEach(function(file, i) { 62 | assert.equal(file.data.pager.index, i); 63 | assert.equal(file.data.pager.current, file); 64 | assert.equal(file.data.pager.prev, files[i - 1]); 65 | assert.equal(file.data.pager.next, files[i + 1]); 66 | assert.equal(file.data.pager.first, files[0]); 67 | assert.equal(file.data.pager.last, files[files.length - 1]); 68 | 69 | switch (i) { 70 | case 0: 71 | 72 | assert(file.data.pager.isFirst); 73 | assert(!file.data.pager.isLast); 74 | break; 75 | case (files.length - 1): 76 | 77 | assert(file.data.pager.isLast); 78 | assert(!file.data.pager.isFist); 79 | break; 80 | default: { 81 | 82 | assert(!file.data.pager.isLast); 83 | assert(!file.data.pager.isFist); 84 | break; 85 | } 86 | } 87 | }); 88 | 89 | cb(); 90 | }); 91 | 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to assemble-pager 2 | 3 | First and foremost, thank you! We appreciate that you want to contribute to assemble-pager, your time is valuable, and your contributions mean a lot to us. 4 | 5 | **What does "contributing" mean?** 6 | 7 | Creating an issue is the simplest form of contributing to a project. But there are many ways to contribute, including the following: 8 | 9 | - Updating or correcting documentation 10 | - Feature requests 11 | - Bug reports 12 | 13 | If you'd like to learn more about contributing in general, the [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) has a lot of useful information. 14 | 15 | **Showing support for assemble-pager** 16 | 17 | Please keep in mind that open source software is built by people like you, who spend their free time creating things the rest the community can use. 18 | 19 | Don't have time to contribute? No worries, here are some other ways to show your support for assemble-pager: 20 | 21 | - star the [project](https://github.com/jonschlinkert/assemble-pager) 22 | - tweet your support for assemble-pager 23 | 24 | ## Issues 25 | 26 | ### Before creating an issue 27 | 28 | Please try to determine if the issue is caused by an underlying library, and if so, create the issue there. Sometimes this is difficult to know. We only ask that you attempt to give a reasonable attempt to find out. Oftentimes the readme will have advice about where to go to create issues. 29 | 30 | Try to follow these guidelines 31 | 32 | - **Investigate the issue**: 33 | - **Check the readme** - oftentimes you will find notes about creating issues, and where to go depending on the type of issue. 34 | - Create the issue in the appropriate repository. 35 | 36 | ### Creating an issue 37 | 38 | Please be as descriptive as possible when creating an issue. Give us the information we need to successfully answer your question or address your issue by answering the following in your issue: 39 | 40 | - **version**: please note the version of assemble-pager are you using 41 | - **extensions, plugins, helpers, etc** (if applicable): please list any extensions you're using 42 | - **error messages**: please paste any error messages into the issue, or a [gist](https://gist.github.com/) 43 | 44 | ## Above and beyond 45 | 46 | Here are some tips for creating idiomatic issues. Taking just a little bit extra time will make your issue easier to read, easier to resolve, more likely to be found by others who have the same or similar issue in the future. 47 | 48 | - read the [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) 49 | - take some time to learn basic markdown. This [markdown cheatsheet](https://gist.github.com/jonschlinkert/5854601) is super helpful, as is the GitHub guide to [basic markdown](https://help.github.com/articles/markdown-basics/). 50 | - Learn about [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/). And if you want to really go above and beyond, read [mastering markdown](https://guides.github.com/features/mastering-markdown/). 51 | - use backticks to wrap code. This ensures that code will retain its format, making it much more readable to others 52 | - use syntax highlighting by adding the correct language name after the first "code fence" 53 | 54 | 55 | [node-glob]: https://github.com/isaacs/node-glob 56 | [micromatch]: https://github.com/jonschlinkert/micromatch 57 | [so]: http://stackoverflow.com/questions/tagged/assemble-pager -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "modules": true, 4 | "experimentalObjectRestSpread": true 5 | }, 6 | 7 | "env": { 8 | "browser": false, 9 | "es6": true, 10 | "node": true, 11 | "mocha": true 12 | }, 13 | 14 | "globals": { 15 | "document": false, 16 | "navigator": false, 17 | "window": false 18 | }, 19 | 20 | "rules": { 21 | "accessor-pairs": 2, 22 | "arrow-spacing": [2, { "before": true, "after": true }], 23 | "block-spacing": [2, "always"], 24 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 25 | "comma-dangle": [2, "never"], 26 | "comma-spacing": [2, { "before": false, "after": true }], 27 | "comma-style": [2, "last"], 28 | "constructor-super": 2, 29 | "curly": [2, "multi-line"], 30 | "dot-location": [2, "property"], 31 | "eol-last": 2, 32 | "eqeqeq": [2, "allow-null"], 33 | "generator-star-spacing": [2, { "before": true, "after": true }], 34 | "handle-callback-err": [2, "^(err|error)$" ], 35 | "indent": [2, 2, { "SwitchCase": 1 }], 36 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 37 | "keyword-spacing": [2, { "before": true, "after": true }], 38 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 39 | "new-parens": 2, 40 | "no-array-constructor": 2, 41 | "no-caller": 2, 42 | "no-class-assign": 2, 43 | "no-cond-assign": 2, 44 | "no-const-assign": 2, 45 | "no-control-regex": 2, 46 | "no-debugger": 2, 47 | "no-delete-var": 2, 48 | "no-dupe-args": 2, 49 | "no-dupe-class-members": 2, 50 | "no-dupe-keys": 2, 51 | "no-duplicate-case": 2, 52 | "no-empty-character-class": 2, 53 | "no-eval": 2, 54 | "no-ex-assign": 2, 55 | "no-extend-native": 2, 56 | "no-extra-bind": 2, 57 | "no-extra-boolean-cast": 2, 58 | "no-extra-parens": [2, "functions"], 59 | "no-fallthrough": 2, 60 | "no-floating-decimal": 2, 61 | "no-func-assign": 2, 62 | "no-implied-eval": 2, 63 | "no-inner-declarations": [2, "functions"], 64 | "no-invalid-regexp": 2, 65 | "no-irregular-whitespace": 2, 66 | "no-iterator": 2, 67 | "no-label-var": 2, 68 | "no-labels": 2, 69 | "no-lone-blocks": 2, 70 | "no-mixed-spaces-and-tabs": 2, 71 | "no-multi-spaces": 2, 72 | "no-multi-str": 2, 73 | "no-multiple-empty-lines": [2, { "max": 1 }], 74 | "no-native-reassign": 0, 75 | "no-negated-in-lhs": 2, 76 | "no-new": 2, 77 | "no-new-func": 2, 78 | "no-new-object": 2, 79 | "no-new-require": 2, 80 | "no-new-wrappers": 2, 81 | "no-obj-calls": 2, 82 | "no-octal": 2, 83 | "no-octal-escape": 2, 84 | "no-proto": 0, 85 | "no-redeclare": 2, 86 | "no-regex-spaces": 2, 87 | "no-return-assign": 2, 88 | "no-self-compare": 2, 89 | "no-sequences": 2, 90 | "no-shadow-restricted-names": 2, 91 | "no-spaced-func": 2, 92 | "no-sparse-arrays": 2, 93 | "no-this-before-super": 2, 94 | "no-throw-literal": 2, 95 | "no-trailing-spaces": 0, 96 | "no-undef": 2, 97 | "no-undef-init": 2, 98 | "no-unexpected-multiline": 2, 99 | "no-unneeded-ternary": [2, { "defaultAssignment": false }], 100 | "no-unreachable": 2, 101 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 102 | "no-useless-call": 0, 103 | "no-with": 2, 104 | "one-var": [0, { "initialized": "never" }], 105 | "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], 106 | "padded-blocks": [0, "never"], 107 | "quotes": [2, "single", "avoid-escape"], 108 | "radix": 2, 109 | "semi": [2, "always"], 110 | "semi-spacing": [2, { "before": false, "after": true }], 111 | "space-before-blocks": [2, "always"], 112 | "space-before-function-paren": [2, "never"], 113 | "space-in-parens": [2, "never"], 114 | "space-infix-ops": 2, 115 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 116 | "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], 117 | "use-isnan": 2, 118 | "valid-typeof": 2, 119 | "wrap-iife": [2, "any"], 120 | "yoda": [2, "never"] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 | Adds data to the context to enable creating pagers that work like this: 2 | 3 | ![image](https://cloud.githubusercontent.com/assets/383994/22801836/730eacdc-eedc-11e6-9dbb-3919875ef9a7.png) 4 | 5 | 6 | ## Usage 7 | 8 | ```js 9 | var assemble = require('assemble'); 10 | var pager = require('{%= name %}'); 11 | var app = assemble(); 12 | 13 | // register the plugin 14 | app.use(pager()); 15 | 16 | app.task('site', function() { 17 | return app.src('src/*.md') 18 | .pipe(app.pager()) // then add "app.pager()" before "app.renderFile()" 19 | .pipe(app.renderFile()) 20 | .pipe(app.dest('blog')); 21 | }); 22 | ``` 23 | 24 | 25 | When `app.pager()` is called, it adds the `pager` variable to the context so that it's available to all views in the collection being rendered. 26 | 27 | 28 | ## Specify a collection 29 | 30 | ```js 31 | // only add paging to the "posts" collection 32 | .pipe(app.pager({collection: 'posts'})) 33 | ``` 34 | 35 | 36 | ## Excluding files 37 | 38 | If you don't [specify a collection](#specify-a-collection), all files in the stream will be buffered and included. However, you can selectively exclude files by setting `file.data.pager` to `false`, either in another plugin, middleware, or by defining it on yaml front-matter. 39 | 40 | **Examples** 41 | 42 | ```js 43 | // plugin, defined before `app.pager()` 44 | .pipe(through.obj(function(file, enc, next) { 45 | file.data.pager = false; 46 | next(null, file); 47 | })) 48 | 49 | // middleware 50 | app.onLoad(/\.foo$/, function(file, next) { 51 | file.data.pager = false; 52 | next(); 53 | }); 54 | ``` 55 | 56 | Or yaml front-matter 57 | 58 | ```handlebars 59 | --- 60 | pager: false 61 | --- 62 | 63 | This is a page that should be skipped by `app.pager()` 64 | ``` 65 | 66 | 67 | ## Template usage 68 | 69 | The `pager` variable is an object with the following properties: 70 | 71 | - `pager.prev` **{Object}** - the previous view that was rendered. This is an actual view, so you can get any data you need from it (like `pager.prev.path` or `pager.prev.data.title`, etc) 72 | - `pager.next` **{Object}** - the next view to be rendered. This is an actual view, so you can get any data you need from it (like `pager.next.path` or `pager.next.data.title`, etc) 73 | - `pager.current` **{Object}** - the view that is currently being rendered 74 | - `pager.first` **{Object}** - Returns the first file in the list 75 | - `pager.last` **{Object}** - Returns the last file in the list 76 | - `pager.isFirst` **{Boolean}** - returns true if the "current" file is the first to be rendered 77 | - `pager.isLast` **{Boolean}** - returns true if the "current" file is the last to be rendered 78 | - `pager.index` **{Number}** - the list index of the current file being rendered 79 | 80 | ### Helpers 81 | 82 | Some helpers are included with this plugin. To use them, do the following: 83 | 84 | ```js 85 | var pager = require('assemble-pager'); 86 | app.helpers(pager.helpers); 87 | ``` 88 | 89 | Which adds the following helpers: 90 | 91 | - `relative`- calculates the relative path from file `A` to file `B` (e.g. "current" file to next or previous file) 92 | - `ifFirst`- returns true if the current file being rendered is the first file 93 | - `ifLast`- returns true if the current file being rendered is the last file 94 | - `next`- used in `href=""`, returns either the relative path to the "next" file, or `#` if the current file being rendered is the last file 95 | - `prev`- used in `href=""`, returns either the relative path to the "previous" file, or `#` if the current file being rendered is the first file 96 | - `attr`- stringify the options hash arguments to HTML attributes. For example, you can use this to conditionally add a class for a disabled next/prev link: `{{ifFirst (attr class="disabled")}}`, or `{{ifLast (attr class="disabled")}}` etc. 97 | 98 | 99 | **Overriding helpers** 100 | 101 | Helpers are exposed as an object so that you can choose to register them if you want, and/or override them if necessary: 102 | 103 | For example, this is how the included "relative" helper works: 104 | 105 | ```js 106 | var relative = require('relative'); 107 | 108 | app.helper('relative', function(from, to) { 109 | return relative(from.data.path, to.data.path); 110 | }); 111 | ``` 112 | 113 | ### Example usage 114 | 115 | Since `pager` is just another variable on the context, you can do anything you normally would with a variable in your handlebars templates. 116 | 117 | 118 | ```handlebars 119 | 121 | Prev 122 | ``` 123 | 124 | Or you can create a block: 125 | 126 | ```handlebars 127 | {{#pager}} 128 | 129 | Prev 130 | {{/pager}} 131 | ``` 132 | 133 | **Partials** 134 | 135 | To simplify paging even more, you can define partials like the following: 136 | 137 | ```js 138 | app.partial('prev', 'Prev'); 139 | app.partial('next', '\nNext'); 140 | app.partial('pager', '{{> prev }}\n{{> next }}'); 141 | ``` 142 | 143 | ### Troubleshooting 144 | 145 | The easiest way to see how this works is to log out the `pager` variables, to see what's on the context. You can use the built-in `log` helper to do this: 146 | 147 | ```handlebars 148 | {{log pager}} 149 | ``` 150 | 151 | ## How it works 152 | 153 | This is really all the plugin does, with the addition of error and options handling (a commented version is below). You can copy this and create your own plugin if you need to: 154 | 155 | ```js 156 | function pagerPlugin() { 157 | var list = new app.List({pager: true}); 158 | return through.obj(function(file, enc, next) { 159 | list.addItem(file); 160 | next(); 161 | }, function(cb) { 162 | for (var i = 0; i < list.items.length; i++) { 163 | this.push(item); 164 | } 165 | cb(); 166 | }); 167 | } 168 | ``` 169 | 170 | With comments: 171 | 172 | ```js 173 | function pagerPlugin() { 174 | // create a new assemble `List`. Lists are like collections, 175 | // but the items are stored as an array, versus an object 176 | var list = new app.List({pager: true}); 177 | 178 | return through.obj(function(file, enc, next) { 179 | // add files to the list as they come through the stream 180 | list.addItem(file); 181 | 182 | // don't pass the file through, we'll do 183 | // that in the flush function 184 | next(); 185 | }, function(cb) { 186 | 187 | // all of the files have now been buffered onto the 188 | // `list.items` array. We can now loop over that array 189 | // and push the items back into the stream (it's important 190 | // for all the items to be buffered this way so that all 191 | // items are available on the context before anything is 192 | // rendered, or items will be missing) 193 | for (var i = 0; i < list.items.length; i++) { 194 | this.push(item); //<= push the item into the stream 195 | } 196 | cb(); 197 | }); 198 | } 199 | ``` 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # assemble-pager [![NPM version](https://img.shields.io/npm/v/assemble-pager.svg?style=flat)](https://www.npmjs.com/package/assemble-pager) [![NPM monthly downloads](https://img.shields.io/npm/dm/assemble-pager.svg?style=flat)](https://npmjs.org/package/assemble-pager) [![NPM total downloads](https://img.shields.io/npm/dt/assemble-pager.svg?style=flat)](https://npmjs.org/package/assemble-pager) [![Linux Build Status](https://img.shields.io/travis/assemble/assemble-pager.svg?style=flat&label=Travis)](https://travis-ci.org/assemble/assemble-pager) 2 | 3 | > Assemble plugin that adds prev/next pager information to the context. 4 | 5 | ## Table of Contents 6 | 7 | - [Install](#install) 8 | - [Usage](#usage) 9 | - [Specify a collection](#specify-a-collection) 10 | - [Excluding files](#excluding-files) 11 | - [Template usage](#template-usage) 12 | * [Helpers](#helpers) 13 | * [Example usage](#example-usage) 14 | * [Troubleshooting](#troubleshooting) 15 | - [How it works](#how-it-works) 16 | - [About](#about) 17 | 18 | _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ 19 | 20 | ## Install 21 | 22 | Install with [npm](https://www.npmjs.com/): 23 | 24 | ```sh 25 | $ npm install --save assemble-pager 26 | ``` 27 | 28 | Adds data to the context to enable creating pagers that work like this: 29 | 30 | ![image](https://cloud.githubusercontent.com/assets/383994/22801836/730eacdc-eedc-11e6-9dbb-3919875ef9a7.png) 31 | 32 | ## Usage 33 | 34 | ```js 35 | var assemble = require('assemble'); 36 | var pager = require('assemble-pager'); 37 | var app = assemble(); 38 | 39 | // register the plugin 40 | app.use(pager()); 41 | 42 | app.task('site', function() { 43 | return app.src('src/*.md') 44 | .pipe(app.pager()) // then add "app.pager()" before "app.renderFile()" 45 | .pipe(app.renderFile()) 46 | .pipe(app.dest('blog')); 47 | }); 48 | ``` 49 | 50 | When `app.pager()` is called, it adds the `pager` variable to the context so that it's available to all views in the collection being rendered. 51 | 52 | ## Specify a collection 53 | 54 | ```js 55 | // only add paging to the "posts" collection 56 | .pipe(app.pager({collection: 'posts'})) 57 | ``` 58 | 59 | ## Excluding files 60 | 61 | If you don't [specify a collection](#specify-a-collection), all files in the stream will be buffered and included. However, you can selectively exclude files by setting `file.data.pager` to `false`, either in another plugin, middleware, or by defining it on yaml front-matter. 62 | 63 | **Examples** 64 | 65 | ```js 66 | // plugin, defined before `app.pager()` 67 | .pipe(through.obj(function(file, enc, next) { 68 | file.data.pager = false; 69 | next(null, file); 70 | })) 71 | 72 | // middleware 73 | app.onLoad(/\.foo$/, function(file, next) { 74 | file.data.pager = false; 75 | next(); 76 | }); 77 | ``` 78 | 79 | Or yaml front-matter 80 | 81 | ```handlebars 82 | --- 83 | pager: false 84 | --- 85 | 86 | This is a page that should be skipped by `app.pager()` 87 | ``` 88 | 89 | ## Template usage 90 | 91 | The `pager` variable is an object with the following properties: 92 | 93 | * `pager.prev` **{Object}** - the previous view that was rendered. This is an actual view, so you can get any data you need from it (like `pager.prev.path` or `pager.prev.data.title`, etc) 94 | * `pager.next` **{Object}** - the next view to be rendered. This is an actual view, so you can get any data you need from it (like `pager.next.path` or `pager.next.data.title`, etc) 95 | * `pager.current` **{Object}** - the view that is currently being rendered 96 | * `pager.first` **{Object}** - Returns the first file in the list 97 | * `pager.last` **{Object}** - Returns the last file in the list 98 | * `pager.isFirst` **{Boolean}** - returns true if the "current" file is the first to be rendered 99 | * `pager.isLast` **{Boolean}** - returns true if the "current" file is the last to be rendered 100 | * `pager.index` **{Number}** - the list index of the current file being rendered 101 | 102 | ### Helpers 103 | 104 | Some helpers are included with this plugin. To use them, do the following: 105 | 106 | ```js 107 | var pager = require('assemble-pager'); 108 | app.helpers(pager.helpers); 109 | ``` 110 | 111 | Which adds the following helpers: 112 | 113 | * `relative`- calculates the relative path from file `A` to file `B` (e.g. "current" file to next or previous file) 114 | * `ifFirst`- returns true if the current file being rendered is the first file 115 | * `ifLast`- returns true if the current file being rendered is the last file 116 | * `next`- used in `href=""`, returns either the relative path to the "next" file, or `#` if the current file being rendered is the last file 117 | * `prev`- used in `href=""`, returns either the relative path to the "previous" file, or `#` if the current file being rendered is the first file 118 | * `attr`- stringify the options hash arguments to HTML attributes. For example, you can use this to conditionally add a class for a disabled next/prev link: `{{ifFirst (attr class="disabled")}}`, or `{{ifLast (attr class="disabled")}}` etc. 119 | 120 | **Overriding helpers** 121 | 122 | Helpers are exposed as an object so that you can choose to register them if you want, and/or override them if necessary: 123 | 124 | For example, this is how the included "relative" helper works: 125 | 126 | ```js 127 | var relative = require('relative'); 128 | 129 | app.helper('relative', function(from, to) { 130 | return relative(from.data.path, to.data.path); 131 | }); 132 | ``` 133 | 134 | ### Example usage 135 | 136 | Since `pager` is just another variable on the context, you can do anything you normally would with a variable in your handlebars templates. 137 | 138 | ```handlebars 139 | 141 | Prev 142 | ``` 143 | 144 | Or you can create a block: 145 | 146 | ```handlebars 147 | {{#pager}} 148 | 149 | Prev 150 | {{/pager}} 151 | ``` 152 | 153 | **Partials** 154 | 155 | To simplify paging even more, you can define partials like the following: 156 | 157 | ```js 158 | app.partial('prev', 'Prev'); 159 | app.partial('next', '\nNext'); 160 | app.partial('pager', '{{> prev }}\n{{> next }}'); 161 | ``` 162 | 163 | ### Troubleshooting 164 | 165 | The easiest way to see how this works is to log out the `pager` variables, to see what's on the context. You can use the built-in `log` helper to do this: 166 | 167 | ```handlebars 168 | {{log pager}} 169 | ``` 170 | 171 | ## How it works 172 | 173 | This is really all the plugin does, with the addition of error and options handling (a commented version is below). You can copy this and create your own plugin if you need to: 174 | 175 | ```js 176 | function pagerPlugin() { 177 | var list = new app.List({pager: true}); 178 | return through.obj(function(file, enc, next) { 179 | list.addItem(file); 180 | next(); 181 | }, function(cb) { 182 | for (var i = 0; i < list.items.length; i++) { 183 | this.push(item); 184 | } 185 | cb(); 186 | }); 187 | } 188 | ``` 189 | 190 | With comments: 191 | 192 | ```js 193 | function pagerPlugin() { 194 | // create a new assemble `List`. Lists are like collections, 195 | // but the items are stored as an array, versus an object 196 | var list = new app.List({pager: true}); 197 | 198 | return through.obj(function(file, enc, next) { 199 | // add files to the list as they come through the stream 200 | list.addItem(file); 201 | 202 | // don't pass the file through, we'll do 203 | // that in the flush function 204 | next(); 205 | }, function(cb) { 206 | 207 | // all of the files have now been buffered onto the 208 | // `list.items` array. We can now loop over that array 209 | // and push the items back into the stream (it's important 210 | // for all the items to be buffered this way so that all 211 | // items are available on the context before anything is 212 | // rendered, or items will be missing) 213 | for (var i = 0; i < list.items.length; i++) { 214 | this.push(item); //<= push the item into the stream 215 | } 216 | cb(); 217 | }); 218 | } 219 | ``` 220 | 221 | ## About 222 | 223 | ### Contributing 224 | 225 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). 226 | 227 | Please read the [contributing guide](.github/contributing.md) for advice on opening issues, pull requests, and coding standards. 228 | 229 | ### Building docs 230 | 231 | _(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_ 232 | 233 | To generate the readme, run the following command: 234 | 235 | ```sh 236 | $ npm install -g verbose/verb#dev verb-generate-readme && verb 237 | ``` 238 | 239 | ### Running tests 240 | 241 | Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: 242 | 243 | ```sh 244 | $ npm install && npm test 245 | ``` 246 | 247 | ### Author 248 | 249 | **Jon Schlinkert** 250 | 251 | * [github/jonschlinkert](https://github.com/jonschlinkert) 252 | * [twitter/jonschlinkert](https://twitter.com/jonschlinkert) 253 | 254 | ### License 255 | 256 | Copyright © 2017, [Jon Schlinkert](https://github.com/jonschlinkert). 257 | MIT 258 | 259 | *** 260 | 261 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.4.2, on February 09, 2017._ --------------------------------------------------------------------------------