Jekyll::Paginate::Content
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.
- Jekyll::Paginate::Content
- TL;DR
- Manual
- Automatic, with config overrides and mixed syntax
- Why use this?
- Installation
- Configuration
- Usage
- Setting up splitting
- Manual mode
- HTML header mode
- Page headers and footers
- Paginator Properties/Fields
- site.baseurl
- Page/post properties
- Setting custom properties
- Overriding and restoring properties
- Special values
- Default properties
- Example: blog
- Example: slides/presentation
- Last slide
- Pagination trails
- Usage
- Page flipper
- Table Of Contents (TOC)
- Excluding sections
- Search Engine Optimization (SEO)
- Unified approach
- Demos
- Limitations
- Contributing
- License
- Code of Conduct
- Also by the Author
- TL;DR
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
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.
See other demos.
Why use this?
- You want to split long posts and pages/articles/reviews, etc. into multiple pages, e.g. chapters;
- You want to offer faster loading times to your readers;
- You want to make slide/presentation-type content (see demo!)
- You want more ad revenue from your Jekyll site;
- 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 %}
« <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> »
{% 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 justpaginator.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:
- Convert the section name to lowercase
- Remove all punctuation
- Convert multiple spaces to a single space
- Convert spaces to dashes
- 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
- TL;DR demos: manual, automatic
- Simple example as a post, as an item in a collection, and as a page.
- This README, autopaginated
- Simple Slides in Jekyll
Limitations
- Some link/anchor formats may not be supported yet; inform author, please.
- 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
- Fork this project: https://github.com/ibrado/jekyll-paginate-content/fork
- Clone it (
git clone git://github.com/your_user_name/jekyll-paginate-content.git
) cd jekyll-paginate-content
- Create a new branch (e.g.
git checkout -b my-bug-fix
) - Make your changes
- Commit your changes (
git commit -m "Bug fix"
) - Build it (
gem build jekyll-paginate-content.gemspec
) - Install and test it (
gem install ./jekyll-paginate-content-*.gem
) - Repeat from step 5 as necessary
- Push the branch (
git push -u origin my-bug-fix
) - 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.