Skip to content

Instantly share code, notes, and snippets.

@markwk
Created February 26, 2021 18:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markwk/52d00f3f4e1ef1598fc2fb58f5b8ab7c to your computer and use it in GitHub Desktop.
Save markwk/52d00f3f4e1ef1598fc2fb58f5b8ab7c to your computer and use it in GitHub Desktop.
Generate Category Pages for Jekyll Blog

README: Jekyll categories pages generator

If you want to created dedicated category pages for your Jekyll blog, here are the steps to make it happen:

  1. Create a file named GenerateCategoryPages.rb, add it to the _plugins directory and copy and paste the snippet below.
  2. Create a file named category_index.html, add it to the _layouts directory and copy and paste the snippet below.
  3. Regenerate your website using jekyll serve or bundle exec jekyll serve

NOTE about YAML formatting in Posts

This script was tested as working using the following YAML format in my posts:

categories:
  - Education
  - History
  - Politics

It may or may not work with alternative methods.

---
layout: default
---
<section>
<header>
<h1 class="category">Category: {{ page.title }}</h1>
</header>
<ul>
{% for post in paginator.posts %}
<li><a href="{{ post.url | prepend: site.baseurl }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
</section>
{% if paginator.total_pages > 1 %}
<nav class="pagination">
{% if paginator.previous_page_path %}
<a class="button previous" href="{{ paginator.previous_page_path }}" title="Previous posts">previous </a>
{% endif %}
{% if paginator.next_page_path %}
<a class="button next" href="{{ paginator.next_page_path }}" title="Next posts"> next</a>
{% endif %}
</nav>
{% endif %}
# encoding: utf-8
#
# Jekyll categories pages generator
#
# This plugin generates pages for each of the categories of your site, as
# defined in their YAML front matter.
# If enabled through configuration, it will also generate the appropriate pagination
#
# To enable category pages generation, add the following in your _config.yml
#
# category_path: "categories/:cat"
#
# If you want to customise the numbered page name, you can use the following:
# Default is "page:num"
#
# category_page_path: "page:num"
#
# Customise those to suit your needs, but you need to keep the placeholders
# as they are: ":cat" ad ":num"
#
# To enable pagination and define the number of posts per page, use:
#
# paginate_categories: 4
#
# This plugin uses the standard Jekyll paginator, so you can also print
# pagination links by simply add the following in your
# category_index.html template:
#
#
# {% if paginator.total_pages > 1 %}
# <nav class="pagination">
# {% if paginator.previous_page_path %}
# <a class="button previous" href="{{ paginator.previous_page_path }}" title="Previous posts">previous </a>
# {% endif %}
# {% if paginator.next_page_path %}
# <a class="button next" href="{{ paginator.next_page_path }}" title="Next posts"> next</a>
# {% endif %}
# </nav>
# {% endif %}
module Jekyll
class CategoryPagesGenerator < Generator
safe true
# Generate category pages, paginated if necessary
#
# site - the Jekyll::Site object
def generate(site)
if enabled?(site)
site.categories.each do |category, posts|
paginate(site, category, posts)
end
end
end
# Paginates each category's posts. Renders the index.html file into paginated
# directories, ie: page2/index.html, page3/index.html, etc and adds more
# site-wide data.
#
# site - the Jekyll::Site object
# category - the String category currently being processed
# posts - an array of that category's posts
#
# {"paginator" => { "page" => <Number>,
# "per_page" => <Number>,
# "posts" => [<Post>],
# "total_posts" => <Number>,
# "total_pages" => <Number>,
# "previous_page" => <Number>,
# "previous_page_path" => <Url>,
# "next_page" => <Number>,
# "next_page_path" => <Url> }}
def paginate(site, category, posts)
all_posts = posts.sort_by { |p| p.date }
all_posts.reverse!
if CategoryPager.pagination_enabled?(site)
pages = CategoryPager.calculate_pages(all_posts, site.config['paginate_categories'].to_i)
else
pages = 1
end
(1..pages).each do |num_page|
pager = CategoryPager.new(site, num_page, all_posts, category, pages)
page = CategoryPage.new(site, site.source, "_layouts", category)
page.dir = CategoryPager.paginate_path(site, num_page, category)
page.pager = pager
site.pages << page
end
end
# Determine if category pages generation is enabled
#
# site - the Jekyll::Site object
#
# Returns true if the category_path config value is set
# and if there are cateories, false otherwise.
def enabled?(site)
!site.config['category_path'].nil? &&
site.categories.size > 0
end
end
class CategoryPage < Page
# Initializes a new CategoryPage.
#
# site - the Jekyll::Site object
# base - the String path to the <source>
# category_dir - the String path between <source> and the category folder
# category - the String category currently being processed
def initialize(site, base, category_dir, category)
@site = site
@base = base
@dir = category_dir
@name = 'index.html'
self.process(@name)
self.read_yaml(File.join(base, '_layouts'), 'category_index.html')
self.data['title'] = "#{category}"
end
end
class CategoryPager
attr_reader :page, :per_page, :posts, :total_posts, :total_pages,
:previous_page, :previous_page_path, :next_page_path, :next_page, :category
# Calculate the number of pages.
#
# all_posts - the Array of all Posts.
# per_page - the Integer of entries per page.
#
# Returns the Integer number of pages.
def self.calculate_pages(all_posts, per_page)
(all_posts.size.to_f / per_page.to_i).ceil
end
def self.removeDiacritics (str)
$defaultDiacriticsRemovalMap.each_with_index do |val, index|
str = str.gsub(val['letters'], val['base'])
end
return str
end
def self.getCategorySlug (headline)
headline = removeDiacritics(headline);
headline = headline.downcase
headline = headline.gsub(/,/, '')
headline = headline.gsub(/'/, '')
headline = headline.gsub(/&/, ' and ')
headline = headline.gsub(/%/, ' percent ')
# invalid chars, make into spaces
headline = headline.gsub(/[^a-z0-9\s-]/, ' ')
# convert multiple spaces/hyphens into one space
headline = headline.gsub(/[\s-]+/, ' ').strip
# hyphens
headline = headline.gsub(/\s/, '-')
return headline;
end
# Determine if category pagination is enabled
#
# site - the Jekyll::Site object
#
# Returns true if pagination is enabled and if there are
# cateories, false otherwise.
def self.pagination_enabled?(site)
!site.config['paginate_categories'].nil? &&
site.categories.size > 0
end
# Static: Return the pagination path of the page
#
# site - the Jekyll::Site object
# num_page - the Integer number of pages in the pagination
# category - the String category currently being processed
#
# Returns the pagination path as a string
def self.paginate_path(site, num_page, category)
return nil if num_page.nil?
format = site.config['category_path'].sub(':cat', getCategorySlug(category))
pagePath = site.config['category_page_path'] ? site.config['category_page_path'] : "page:num"
if num_page > 1
format = File.join(format, pagePath)
format = format.sub(':num', num_page.to_s)
end
ensure_leading_slash(format)
end
# Static: Return a String version of the input which has a leading slash.
# If the input already has a forward slash in position zero, it will be
# returned unchanged.
#
# path - a String path
#
# Returns the path with a leading slash
def self.ensure_leading_slash(path)
path[0..0] == "/" ? path : "/#{path}"
end
# Initialize a new Pager.
#
# site - the Jekyll::Site object
# page - the Integer page number.
# all_posts - the Array of all the site's Posts.
# category - the category currently being processed. As a String
# num_pages - the Integer number of pages or nil if you'd like the number
# of pages calculated.
def initialize(site, page, all_posts, category, num_pages = nil)
@category = category
@page = page
@per_page = site.config['paginate_categories'].to_i
@total_pages = num_pages || Pager.calculate_pages(all_posts, @per_page)
if @page > @total_pages
raise RuntimeError, "page number can't be greater than total pages: #{@page} > #{@total_pages}"
end
init = (@page - 1) * @per_page
offset = (init + @per_page - 1) >= all_posts.size ? all_posts.size : (init + @per_page - 1)
@total_posts = all_posts.size
@posts = all_posts[init..offset]
@previous_page = @page != 1 ? @page - 1 : nil
@previous_page_path = CategoryPager.paginate_path(site, @previous_page, category)
@next_page = @page != @total_pages ? @page + 1 : nil
@next_page_path = CategoryPager.paginate_path(site, @next_page, category)
end
# Convert this Pager's data to a Hash suitable for use by Liquid.
#
# Returns the Hash representation of this Pager.
def to_liquid
{
'page' => page,
'per_page' => per_page,
'posts' => posts,
'total_posts' => total_posts,
'total_pages' => total_pages,
'previous_page' => previous_page,
'previous_page_path' => previous_page_path,
'next_page' => next_page,
'next_page_path' => next_page_path,
'category' => category
}
end
end
end
$defaultDiacriticsRemovalMap = [
{'base' => 'A', 'letters' => /[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/},
{'base' => 'AA','letters' => /[\uA732]/},
{'base' => 'AE','letters' => /[\u00C6\u01FC\u01E2]/},
{'base' => 'AO','letters' => /[\uA734]/},
{'base' => 'AU','letters' => /[\uA736]/},
{'base' => 'AV','letters' => /[\uA738\uA73A]/},
{'base' => 'AY','letters' => /[\uA73C]/},
{'base' => 'B', 'letters' => /[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/},
{'base' => 'C', 'letters' => /[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/},
{'base' => 'D', 'letters' => /[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/},
{'base' => 'DZ','letters' => /[\u01F1\u01C4]/},
{'base' => 'Dz','letters' => /[\u01F2\u01C5]/},
{'base' => 'E', 'letters' => /[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/},
{'base' => 'F', 'letters' => /[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/},
{'base' => 'G', 'letters' => /[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/},
{'base' => 'H', 'letters' => /[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/},
{'base' => 'I', 'letters' => /[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/},
{'base' => 'J', 'letters' => /[\u004A\u24BF\uFF2A\u0134\u0248]/},
{'base' => 'K', 'letters' => /[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/},
{'base' => 'L', 'letters' => /[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/},
{'base' => 'LJ','letters' => /[\u01C7]/},
{'base' => 'Lj','letters' => /[\u01C8]/},
{'base' => 'M', 'letters' => /[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/},
{'base' => 'N', 'letters' => /[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/},
{'base' => 'NJ','letters' => /[\u01CA]/},
{'base' => 'Nj','letters' => /[\u01CB]/},
{'base' => 'O', 'letters' => /[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/},
{'base' => 'OI','letters' => /[\u01A2]/},
{'base' => 'OO','letters' => /[\uA74E]/},
{'base' => 'OU','letters' => /[\u0222]/},
{'base' => 'P', 'letters' => /[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/},
{'base' => 'Q', 'letters' => /[\u0051\u24C6\uFF31\uA756\uA758\u024A]/},
{'base' => 'R', 'letters' => /[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/},
{'base' => 'S', 'letters' => /[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/},
{'base' => 'T', 'letters' => /[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/},
{'base' => 'TZ','letters' => /[\uA728]/},
{'base' => 'U', 'letters' => /[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/},
{'base' => 'V', 'letters' => /[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/},
{'base' => 'VY','letters' => /[\uA760]/},
{'base' => 'W', 'letters' => /[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/},
{'base' => 'X', 'letters' => /[\u0058\u24CD\uFF38\u1E8A\u1E8C]/},
{'base' => 'Y', 'letters' => /[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/},
{'base' => 'Z', 'letters' => /[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/},
{'base' => 'a', 'letters' => /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/},
{'base' => 'aa','letters' => /[\uA733]/},
{'base' => 'ae','letters' => /[\u00E6\u01FD\u01E3]/},
{'base' => 'ao','letters' => /[\uA735]/},
{'base' => 'au','letters' => /[\uA737]/},
{'base' => 'av','letters' => /[\uA739\uA73B]/},
{'base' => 'ay','letters' => /[\uA73D]/},
{'base' => 'b', 'letters' => /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/},
{'base' => 'c', 'letters' => /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/},
{'base' => 'd', 'letters' => /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/},
{'base' => 'dz','letters' => /[\u01F3\u01C6]/},
{'base' => 'e', 'letters' => /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/},
{'base' => 'f', 'letters' => /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/},
{'base' => 'g', 'letters' => /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/},
{'base' => 'h', 'letters' => /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/},
{'base' => 'hv','letters' => /[\u0195]/},
{'base' => 'i', 'letters' => /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/},
{'base' => 'j', 'letters' => /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/},
{'base' => 'k', 'letters' => /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/},
{'base' => 'l', 'letters' => /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/},
{'base' => 'lj','letters' => /[\u01C9]/},
{'base' => 'm', 'letters' => /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/},
{'base' => 'n', 'letters' => /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/},
{'base' => 'nj','letters' => /[\u01CC]/},
{'base' => 'o', 'letters' => /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/},
{'base' => 'oi','letters' => /[\u01A3]/},
{'base' => 'ou','letters' => /[\u0223]/},
{'base' => 'oo','letters' => /[\uA74F]/},
{'base' => 'p','letters' => /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/},
{'base' => 'q','letters' => /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/},
{'base' => 'r','letters' => /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/},
{'base' => 's','letters' => /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/},
{'base' => 't','letters' => /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/},
{'base' => 'tz','letters' => /[\uA729]/},
{'base' => 'u','letters' => /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/},
{'base' => 'v','letters' => /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/},
{'base' => 'vy','letters' => /[\uA761]/},
{'base' => 'w','letters' => /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/},
{'base' => 'x','letters' => /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/},
{'base' => 'y','letters' => /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/},
{'base' => 'z','letters' => /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment