Paginate::Content - Split your Jekyll pages, posts, etc. into multiple pages automatically. Single-page view, pager, SEO support, self-adjusting links, multipage-aware Table Of Contents.
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
bin
example
lib
res
spec
.gitignore
.rspec
.travis.yml
CODE_OF_CONDUCT.md
Gemfile
LICENSE.txt
README.md
Rakefile
jekyll-paginate-content.gemspec

README.md

Jekyll::Paginate::Content

Gem Version

You may read this documentation split across several pages.

Jekyll::Paginate::Content (JPC) is a plugin for Jekyll that automatically splits pages, posts, and other content into one or more pages. This can be at points where e.g. <!--page--> is inserted, or at the <h1> to <h6> headers. It mimics jekyll-paginate-v2 (JPv2) naming conventions and features, so if you use that, you will be in familiar territory.

Features: Automatic content splitting into several pages, single-page view, configurable permalinks, page trail/pager, SEO support, self-adjusting internal links, multipage-aware Table Of Contents.

TL;DR

Manual

---
title: "JPC demo: 3-page manual"
layout: page
paginate: true
---

This shows up at the top of all pages.
<!--page_header-->

This is page 1 of the JPC example.

<a name="lorem"></a>Lorem ipsum dolor...

<!--page-->
This is page 2 with a [link] to the first page which works in single or paged view.

<!--page-->
This is the last page.

<!--page_footer-->
This goes into the bottom of all pages.

[link]: #lorem

Live demo

Automatic, with config overrides

---
title: "JPC demo: 3-page auto"
layout: page
paginate: true
paginate_content:
  separator: h2
  title: ":title :num/:max: :section"
  permalink: /page:numof:max.html
---

# Introduction

Hello!

## What did something?

The quick brown fox...

## What did it do?

...jumped over the lazy dog.

Live demo

See other demos.

Why use this?

  1. You want to split long posts and pages/articles/reviews, etc. into multiple pages, e.g. chapters;
  2. You want to offer faster loading times to your readers;
  3. You want to make slide/presentation-type content (see demo!)
  4. You want more ad revenue from your Jekyll site;
  5. You wanna be the cool kid. 😛

What's new?

v1.1.0 Layout overrides for e.g. slides; regenerate and other fixes

v1.0.4 Allow inclusion in _config.yml plugins

v1.0.3 Bugfixes; force option

v1.0.2 Don't regenerate unnecessarily

Installation

Add the gem to your application's Gemfile:

group :jekyll_plugins do
  # other plugins here
  gem 'jekyll-paginate-content'
end

And then execute:

$ bundle

Or install it yourself:

$ gem install jekyll-paginate-content

Configuration

No configuration is required to run Jekyll::Paginate::Content. If you want to tweak its behavior, you may set the following options in _config.yml:

paginate_content:
  #enabled: false                    # Default: true
  debug: true                        # Show additional messages during run; default: false
  #collection: pages, "articles"     # Which collections to paginate; default: pages and posts
  collections:                       # Ditto, just a different way of writing it
    - pages                          # Quotes are optional if collection names are obviously strings
    - posts
    - articles

  auto: true                         # Set to true to search for the page separator even if you
                                     #   don't set paginate: true in the front-matter
                                     # Default: false

  force: true                        # Set to true to force regeneration of pages; default: false

  separator: "<!--split-->"          # The page separator; default: "<!--page-->"
                                     # Can be "h1" to "h6" for automatic splitting
                                     # Note: Setext (underline titles) only supports h1 and h2

  header: "<!--head-->"              # The header separator; default: "<!--page_header-->"
  footer: "<!--foot-->"              # The footer separator; default: "<!--page_footer-->"

  permalink: '/borg:numof:max.html'  # Relative path to the new pages; default: "/:num/"
                                     #   :num will be replaced by the current page number
                                     #   :max will be replaced by the total number of page parts
                                     # e.g. /borg7of9.html

  single_page: '/full.html'          # Relative path to the single-page view; default: "/view-all/"
                                     # Set to "" for no single page view

  minimum: 1000                      # Minimum number of characters (including markup) in a page
                                     # for automatic header splitting. 
                                     #   If a section is too short, the next section will be merged
                                     # Default: none
                                     
  title: ':title - :num/:max'        # Title format of the split pages, default: original title
                                     #   :num and :max are as in permalink,
                                     #   :title is the original title
                                     #   :section is the text of the first header

  retitle_first: true                # Should the first part be retitled too? Default: false

  trail:                             # The page trail settings: number of pages to list
    before: 3                        #   before and after the current page
    after: 3                         #   Omit or set to 0 for all pages (default)

  seo_canonical: false               # Set link ref="canonical" to the view-all page; default: true

  prepend_baseurl: false             # Prepend the site.baseurl to paths; default: true

  #properties:                       # Set properties per type of page, see below
  #  all:
  #    field1: value1
  #    # ...etc...
  #  first:
  #    field2: value2
  #    # ...etc...
  #  part:
  #    field3: value3
  #     # ...etc...
  #   last:
  #    field4: value4
  #    # ...etc...
  #  single:
  #    field5: value5
  #    # ...etc...

Here's a cleaned-up version with the defaults:

paginate_content:
  #enabled: true
  #debug: false

  #collections:
  #  - pages
  #  - posts

  #auto: false

  #separator: "<!--page-->"
  #header: "<!--page_header-->"
  #footer: "<!--page_footer-->"

  #permalink: '/:num/"
  #single_page: '/view-all/'
  #minimum: 0

  #title: ':title'
  #retitle_first: false

  #trail:
  #  before: 0
  #  after: 0

  #seo_canonical: true

  #prepend_baseurl: true

  #properties:
  #  all:
  #  first:
  #  part:
  #  last:
  #  single:

Usage

Just add a paginate: true entry to your front-matter:

---
title: Test post
layout: post
date: 2017-12-15 22:33:44
paginate: true
---

or set auto to true in your _config.yml:

paginate_content:
  auto: true

Note that using auto mode might be slower on your machine.

You may also override _config.yml settings for a particular file like so:

---
title: Test page
layout: page
paginate_content:
  permalink: '/page:numof:max.html'
  single_page: '/full.html'
---

You don't need to specify paginate: true if you already have paginate_content in your front matter.

Splitting content

How your content is split depends on your separator:

Manual mode

If your separator is "<!--page-->", just put that wherever you want a split to occur:

This is a page.
<!--page-->
This is another page.

HTML header mode

If you set your header to "h1" up to "h6", your files will be split at those headers. Both the standard atx and Setext formats are supported -- the former uses 1 to 6 hashes (# to ######) at the start for <h1> to <h6>, while the later uses equals-underscores for <h1> and dash-underscores for <h2>

For example, if your separator is "h1":

# Introduction
This is a page.

Discussion
==========
This is another page

## Point 1
This is not.

Point 2
-------
Neither is this

<h1>Conclusion</h1>
But this is.

At this time, you'll need at least 4 dashes for Setext-style <h2>. Note that Setext only supports <h1> and <h2>.

Page headers and footers

Anything above your configured header string will appear at the top of the generated pages. Likewise, anything after your footer string will appear at the bottom.

This is the header
<!--page_header-->

This is a page.
<!--page-->
This is another page.

<!--page_footer-->
This is the footer.

If you split your links like so:

This is a [link].

[link]: https://example.com

make sure you put these referenced link definitions below the footer so that references to them will work across pages.

Minimum page length

You may set the minimum length (in characters) using the minimum property in _config.yml or your front-matter. Should a particular section be too short, the next section will be merged in, and so on until the minimum is reached or there are no more pages.

Note that this length includes markup, not just the actual displayed text, so you may want to take that into consideration. A minimum of 1000 to 2000 should work well.

Paginator Properties/Fields

These properties/fields are available to your layouts and content via the paginator object, e.g. {{ paginator.page }}.

Field Aliases Description
first_page First page number, i.e. 1
first_page_path first_path Relative URL to the first page
next_page Next page number
next_page_path next_path Relative URL to the next page
previous_page prev_page Previous page number
previous_page_path previous_path
prev_path
Relative URL to the previous page
last_page Last page number
last_page_path last_path Relative URL to the last page
page page_num Current page number
page_path Path to the current page
page_trail Page trail, see below
total_pages pages Total number of pages
single_page view_all Path to the original/full page
seo HTML header tags for SEO, see below
toc Table Of Contents generator, see below
section Text of the first header (<h1> etc.) on this page
previous_section prev_section Ditto for the previous page
next_section Ditto for the next page
section_id The header id (<a name>) of this section
paginated activated true if this is a partial page
has_next true if there is a next page
has_previous has_prev true if there is a previous page
is_first true if this is the first page
is_last true if this is the last page
next_is_last true if this page is next-to-last
previous_is_first prev_is_first true if this is the second page

site.baseurl

By default, JPC automatically prepends your site.baseurl to generated paths so you don't have to do it yourself. If you don't like this behavior, set prepend_baseurl: false in your configuration.

Page/Post properties

These properties are automatically set for pages and other content that have been processed, e.g {{ post.autogen }}

Field Description
permalink Relative path of the current page
hidden true for all pages (including the single-page view) except the first page
tag, tags nil for all except the first page
category, categories nil for all except the first page
autogen "jekyll-paginate-content" for all pages
pagination_info .curr_page = current page number
.total_pages = total number of pages
.type = "first", "part", "last", or "single"
.id = a string which is the same for all related pages

The tags, categories, and hidden are set up this way to avoid duplicate counts and having the parts show up in e.g. your tag index listings. You may override this behavior as discussed below.

Setting custom properties

paginate_content has a properties option:

paginate_content:
  properties:
    all:
      field1: value1
      # ...etc...
    first:
      field2: value2
      # ...etc...
    part:
      field3: value3
      # ...etc...
    last:
      field4: value4
      # ...etc...
    single:
      field5: value5
      # ...etc...

where the properties/fields listed under all will be set for all pages, first properties for the first page (possibly overriding values in all), etc.

Example: To help with your layouts, you may want to set a property for the single-page view, say, activating comments:

paginate_content:
  properties:
    single:
      comments: true

In your layout, you'd use something like

{% if post.comments %}
   <!-- Disqus section -->
{% endif %}

The single-page view would then show the Disqus comments section.

Overriding and restoring properties

You can set almost any front-matter property via the properties section, except for title, date, permalink, and pagination_info. Use with caution.

Special values

You may use the following values for properties:

Value Meaning
~ nil (essentially disabling the property)
$ The original value of the property
$.property The original value of the specified property
/ Totally remove this property

Default properties

For reference, the default properties effectively map out to:

  properties:
    all:
      autogen: 'jekyll-paginate-content'
      hidden: true
      tag: ~
      tags: ~
      category: ~
      categories: ~
      pagination_info:
        curr_page: (a number)
        total_pages:  (a number)
        id: '(a string)'

    first:
      hidden: false
      tag: $
      tags: $
      category: $
      categories: $
      pagination_info:
        type: 'first'

    part:
      pagination_info:
        type: 'part'

    last:
      pagination_info:
        type: 'last'

    single:
      pagination_info:
        curr_page: /
        total_pages: /
        type: 'single'

Example: blog

The author's _config.yml has the following:

  properties:
    all:
      comments: false
      share: false
      x_title: $.title

    #first:
      # keeps original tags and categories

    part:
      x_tags: []
      x_cats: []

    last:
      comments: true
      share: true
      x_tags: $.tags
      x_cats: $.categories

    single:
      comments: true
      share: true
      x_tags: $.tags
      x_cats: $.categories

x_tags and x_cats are used in this case to store the original tags and categories for generating a list of related posts only for last pages or single-page views. comments and share are likewise used to turn on the sections for comments and social media sharing for these pages.

x_title saves the original title for use in social media sharing. The example below also does something similar for the URL to be shared:

{% if page.x_title %}
  {% assign share_title = page.x_title %}
{% else %}
  {% assign share_title = page.title %}
{% endif %}

{% if paginator.first_path %}
  {% assign share_url = paginator.first_path %}
{% else %}
  {% assign share_url = page.url %}
{% endif %}

Example: slides/presentation

JPC can be used to generate slides as well as a detailed document from the same source Markdown, i.e.

{% if paginator.paginated %}
  // Content that only shows up in the slides
{% else %}
  // Content that only shows up in the single/details page.
{% endif %}

Or alternatively,

{% if paginator.paginated %}
  // Content that only shows up in the slides
{% endif %}

and

{% unless paginator.paginated %}
  // Content that only shows up in the single/details page.
{% endif %}

Here's an example configuration:

  properties:
    all:
      layout: slides
    single:
      layout: $

This makes all pages except the single-page view use the slides layout. The latter will use the original layout.

Last slide

When using JPC to generate slides, you may use _last_ as the title for the last slide (usually a "thank you" or contact info slide). It will be removed and hidden from the TOC.

The demos include a sample presentation.

Pagination trails

You use paginator.page_trail to create a pager that will allow your readers to move from page to page. It is set up as follows:

paginate_content:
  trail:
    before: 2
    after: 2

before refers to the number of page links you want to appear before the current page, as much as possible. Similarly, after is the number of page links after the current page. So, in the above example, you have 2 before + 1 current + 2 after = 5 links to pages in your trail "window".

If you don't specify the trail properties, or set before and after to 0, all page links will be returned.

Let's say your document has 7 pages, and you have a trail as above. The pager would look something like this as you go from page to page:

« <1> [2] [3] [4] [5] »
« [1] <2> [3] [4] [5] »
« [1] [2] <3> [4] [5] »
« [2] [3] <4> [5] [6] »
« [3] [4] <5> [6] [7] »
« [3] [4] [5] <6> [7] »
« [3] [4] [5] [6] <7> »

Usage

paginator.page_trail has the following fields:

Field Description
num The page number
path The path to the page
title The title of the page

Here is an example adapted from JPv2's documentation. Note that you don't need to prepend site.baseurl to trail.path as it is automatically added in by JPC by default.

{% if paginator.page_trail %}
  <ul class="pager">
  {% for trail in paginator.page_trail %}
    <li {% if page.url == trail.path %}class="selected"{% endif %}>
        <a href="{{ trail.path }}" title="{{ trail.title }}">{{ trail.num }}</a>
    </li>
  {% endfor %}
  </ul>
{% endif %}

Its accompanying style:

<style>
  ul.pager { text-align: center; list-style: none; }
  ul.pager li {display: inline;border: 1px solid black; padding: 10px; margin: 5px;}
  .selected { background-color: magenta; }
</style>

You'll end up with something like this, for page 4:

The author's own pager is a little more involved and uses some convenience fields and aliases:

{% if paginator.page_trail %}
  <div class="pager">
    {% if paginator.is_first %}
      <span class="pager-inactive"><i class="fa fa-fast-backward" aria-hidden="true"></i></span>
      <span class="pager-inactive"><i class="fa fa-backward" aria-hidden="true"></i></span>
    {% else %}
      <a href="{{ paginator.first_path }}"><i class="fa fa-fast-backward" aria-hidden="true"></i></a>
      <a href="{{ paginator.previous_path }}"><i class="fa fa-backward" aria-hidden="true"></i></a>
    {% endif %} 

    {% for p in paginator.page_trail %}
      {% if p.num == paginator.page %}
        {{ p.num }} 
      {% else %}
        <a href="{{ p.path }}" data-toggle="tooltip" data-placement="top" title="{{ p.title }}">{{ p.num }}</a>
      {% endif %}
    {% endfor %}

    {% if paginator.is_last %}
      <span class="pager-inactive"><i class="fa fa-forward" aria-hidden="true"></i></span>
      <span class="pager-inactive"><i class="fa fa-fast-forward" aria-hidden="true"></i></span>
    {% else %}
      <a href="{{ paginator.next_path }}"><i class="fa fa-forward" aria-hidden="true"></i></a>
      <a href="{{ paginator.last_path }}"><i class="fa fa-fast-forward" aria-hidden="true"></i></a>
    {% endif %} 
  </div>
{% endif %}

This results in a pager that looks like this:

Page flipper

You may also want to add a page "flipper" that uses section names:

<!--page_footer-->
<div>
  {% if paginator.previous_section %}
    &laquo; <a href="{{ paginator.previous_path }}">{{ paginator.previous_section }}</a>
  {% endif %}
  {% if paginator.previous_section and paginator.next_section %} | {% endif %}
  {% if paginator.next_section %}
    <a href="{{ paginator.next_path }}">{{ paginator.next_section }}</a> &raquo;
  {% endif %}
</div>

Should there be no available section name, "Untitled" will be returned. You can then handle it like so:

{% if paginator.previous_section == "Untitled" %}
  Previous
{% else %}
  {{ paginator.previous_section }}
{% endif %}

Of course, you always have the option of adding some navigational cues to your content:

{% if paginator.paginated %}
  <a href="{{ paginator.next_page_path }}">On to the next chapter...</a>
{% endif %}

This text will not appear in the single-page view.

Table Of Contents (TOC)

JPC automatically generates a Table of Contents for you. To use this from within your content, simply insert the following:

  {{ paginator.toc.simple }}

If you want to use this from an HTML layout, e.g. an included sidebar.html:

  {{ paginator.toc.simple | markdownify }}

The difference between this and the one built into kramdown, the default Jekyll Markdown engine, is that it is aware that content may be split across several pages now, and adjusts links depending on the current page.

The reason paginator.toc.simple is used vs just paginator.toc is to allow for further TOC features in the future.

Excluding sections

Should you want some sections excluded from the Table Of Contents, add them to the toc_exclude option in your site configuration or content front-matter:

paginate_content:
  toc_exclude: "Table Of Contents"

or

paginate_content:
  toc_exclude: 
    - "Table Of Contents"
    - "Shy Section"

The generated section ids follow the usual convention:

  1. Convert the section name to lowercase
  2. Remove all punctuation
  3. Convert multiple spaces to a single space
  4. Convert spaces to dashes
  5. If that id already exists, add "-1", "-2", etc. until the id is unique

Search Engine Optimization (SEO)

Now that your site features split pages (finally!), how do you optimize it for search engines?

paginator.seo has the following fields:

Field Description
canonical HTML link rel of the canonical URL (primary page for search results)
prev Ditto for the previous page, if applicable
next Ditto for the next page, if applicable
links All of the above, combined

You could simply add the following somewhere inside the <head> of your document:

{{ paginator.seo.links }}

It will produce up to three lines, like so (assuming you are on page 5):

  <link rel="canonical" href="https://example.com/2017/12/my-post/view-all/" />
  <link rel="prev" href="https://example.com/2017/12/my-post/4/" />
  <link rel="next" href="https://example.com/2017/12/my-post/6/" />

rel="prev" and/or rel="next" will not be included if there is no previous and/or next page, respectively. If you don't want to set canonical to the single-view page, just set seo_canonical in your _config.yml to false.

Unified approach

It would be better to use the following approach, though:

{% unless paginator %}
  <link rel="canonical" href="{{ site.canonical }}{{ site.baseurl }}{{ page.url }}" />
{% endunless %}
{% if paginator.seo.links %}
{{ paginator.seo.links }}
{% else %}
  {% if paginator.previous_page_path %}
  <link rel="prev" href="{{ site.url }}{{ site.baseurl }}{{ paginator.previous_page_path }}" />
  {% endif %}
  {% if paginator.next_page_path %}
  <link rel="next" href="{{ site.url }}{{ site.baseurl }}{{ paginator.next_page_path }}" />
  {% endif %}
{% endif %}

This way it works with JPv2, JPC, and with no paginator active.

What about canonical for JPv2-generated pages? Unless you have a "view-all" page that includes all your unpaginated posts and you want search engines to use that possibly huge page as the primary search result, it is probably best to just not put a canonical link at all.

Demos

  1. TL;DR demos: manual, automatic
  2. Simple example as a post, as an item in a collection, and as a page.
  3. This README, autopaginated
  4. Simple Slides in Jekyll

Limitations

  1. Some link/anchor formats may not be supported yet; inform author, please.
  2. The Setext mode, i.e. underscoring header names with equal signs (<h1>) or dashes (<h2>), needs to have at least 4 dashes for <h2>.

Contributing

  1. Fork this project: https://github.com/ibrado/jekyll-paginate-content/fork
  2. Clone it (git clone git://github.com/your_user_name/jekyll-paginate-content.git)
  3. cd jekyll-paginate-content
  4. Create a new branch (e.g. git checkout -b my-bug-fix)
  5. Make your changes
  6. Commit your changes (git commit -m "Bug fix")
  7. Build it (gem build jekyll-paginate-content.gemspec)
  8. Install and test it (gem install ./jekyll-paginate-content-*.gem)
  9. Repeat from step 5 as necessary
  10. Push the branch (git push -u origin my-bug-fix)
  11. Create a Pull Request, making sure to select the proper branch, e.g. my-bug-fix (via https://github.com/your_user_name/jekyll-paginate-content)

Bug reports and pull requests are welcome on GitHub at https://github.com/ibrado/jekyll-paginate-content. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Jekyll::Paginate::Content project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

Also by the Author

Jekyll::Stickyposts - Move/pin posts tagged sticky: true before all others. Sorting on custom fields supported; collection and paginator friendly.

Jekyll::Tweetsert - Turn tweets into Jekyll posts. Multiple timelines, filters, hashtags, automatic category/tags, and more!

Jekyll::ViewSource - Generate pretty or plain HTML and/or Markdown source code pages.