Jekyll - Map multiple URLs to same Jekyll file - jekyll

I have a custom page for Jekyll: I have a news.markdown file like the following:
{% assign posts_per_page = 5 %}
{% for post in site.categories.ro offset:pagination_skip limit:posts_per_page %}
...
{{ post.content }}
...
{% endfor %}
{% include nav.html %}
Where nav.html is in the _includes directory and it looks like this:
{% if pagination_skip %}
{% capture pagination_skip %}
{{pagination_skip | plus: posts_per_page}}
{% endcapture %}
{% else %}
{% assign pagination_skip = posts_per_page %}
{% endif %}
<div class="next">
<a rel="prev" href="{{site.basepath}}ro/news/{{ pagination_skip }}">Next</a>
</div>
What I want is for the url .../ro/news/5 to be mapped to the content of news.markdown such that pagination_skip there is 5. Same for 10, 15, whatever. Moreover, .../ro/news/0 should be the same as .../ro/news/ if possible.
Can I do that? How?
I'd like to use as few extensions as possible.

You try to do it wrong. You want to have a dynamic handling of a URL parameter. Jekyll is a static web generator. So you need to generate all the page that will be visited. You can a generator for that. Please have a look at Generator section on page https://github.com/mojombo/jekyll/wiki/Plugins
I worked on a generator for what you want to do and it works pretty nicely, I also created the 'prev' and 'next' button. Please have a look and try it. It works for all categories (not only the ro one), but you can customize it if you want.
folder structure
| -- _plugins
` news.rb
| -- _layouts
` news.html
| -- _includes
` nav.html
| -- _config.yml
| -- ro
` -- _posts
| -- 2012-04-10-test.textile
| -- 2012-04-10-test2.textile
| -- 2012-04-10-test4.textile
| -- 2012-04-10-test6.textile
| -- 2012-04-10-test15.textile
| -- 2012-04-10-test3.textile
` -- 2012-04-10-test5.textile
` _site
the genrator news.rbruby code
module Jekyll
class NewsPage < Page
def initialize(site, base, dir, category, posts_number, posts_per_page, pagination_skip)
#site = site
#base = base
#dir = dir
#name = "news_#{pagination_skip}.html"
self.process(#name)
self.read_yaml(File.join(base, '_layouts'), 'news.html')
self.data['category'] = category
self.data['posts_per_page'] = posts_per_page
self.data['pagination_skip'] = pagination_skip
if pagination_skip != 0
self.data['prev_pagination_skip'] = pagination_skip - posts_per_page
end
if pagination_skip + posts_per_page < posts_number
self.data['next_pagination_skip'] = pagination_skip + posts_per_page
end
end
end
class NewsGenerator < Generator
safe true
def generate(site)
if site.layouts.key? 'news'
dir = site.config['category_dir'] || 'categories'
site.categories.keys.each do |category|
posts_number = site.categories[category].length
pagination_skip = 0;
posts_per_page = 5;
begin
write_news_page(site, File.join(dir, category), category, posts_number, posts_per_page, pagination_skip)
pagination_skip += posts_per_page;
end while pagination_skip < posts_number
end
end
end
def write_news_page(site, dir, category, posts_number, posts_per_page, pagination_skip)
index = NewsPage.new(site, site.source, dir, category, posts_number, posts_per_page, pagination_skip)
index.render(site.layouts, site.site_payload)
index.write(site.dest)
site.pages << index
end
end
end
The layout of the news.html file
---
---
{% for post in site.categories[page.category] offset:page.pagination_skip limit:page.posts_per_page %}
{{ post.content }}
{% endfor %}
{% include nav.html %}
The include navigation nav.html
<div class="nav">
{% if page.prev_pagination_skip %}
<a rel="prev" href="{{site.basepath}}categories/{{page.category}}/news_{{page.prev_pagination_skip}}.html">Prev</a>
{% endif %}
{% if page.next_pagination_skip %}
<a rel="next" href="{{site.basepath}}categories/{{page.category}}/news_{{page.next_pagination_skip}}.html">Prev</a>
{% endif %}
</div>
Give it a try and let me let me know if you like it.

Related

Grouping if statements

I'm trying to find a way to have the "if contains" conditions all appear in one line rather than having to repeat the code every single time.
Here is what the code looks like:
var selector = document.querySelector('tr[data-variant-id="{{ item.variant.id }}"] .product__description__variant');
{% assign pre_order_message = '' %}
{% for tag in item.product.tags %}
{% if tag contains 'weeks' %}
{% assign count = count | plus: 1 %}
{% assign pre_order_message = tag | split:'**' | last %}
var para = document.createElement("p");
para.classList.add('hc-shipping');
var node = document.createTextNode("Expected to begin shipping {{ pre_order_message }} from order date.");
para.appendChild(node);
selector.after(para);
{% endif %}
{% endfor %}
var selector = document.querySelector('tr[data-variant-id="{{ item.variant.id }}"] .product__description__variant');
{% assign pre_order_message = '' %}
{% for tag in item.product.tags %}
{% if tag contains 'January' %}
{% assign count = count | plus: 1 %}
{% assign pre_order_message = tag | split:'**' | last %}
var para = document.createElement("p");
para.classList.add('hc-shipping');
var node = document.createTextNode("Expected to begin shipping {{ pre_order_message }}.");
para.appendChild(node);
selector.after(para);
{% endif %}
{% endfor %}
I just want to make sure I can group together what lies between the ' ... ' on the line, that states {% if tag contains ' ' %}. Any way to fix? Thanks!
You can use and/or to combine conditions, more info about this here.
By the way if you just want to make sure a tag exists in product tags, you can do this:
{% if item.product.tags contains 'weeks' %}
Do something...
{% endif %}
Instead of looping through all the tags, you'll save a couple of lines that way ;)
One last thing, you can DRY the code by using code snippets, here's a quick read about this.

Kramdown table of content doesn't display inside HTML block

I was following this question, but it doesn't seem to take effect for me. Any help would be greatly appreciated.
_includes/layout.html
<main>
<div>
<!-- Sidebar -->
<aside markdown="1">
<h4>Table of Contents</h4>
* ToC
{:toc}
</aside>
<!-- END Sidebar -->
<!-- Main content -->
<article>
{{ content }}
</article>
<!-- END Main content -->
</div>
</main>
_config.yml
markdown: kramdown
Result:
Update
_layouts/site.html
<aside markdown="1">
mark**down**
</aside>
It just renders as above. Kramdown is turned on in config.
Solved by using pure liquid ToC by allejo.
% capture tocWorkspace %}
{% comment %}
"...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe
Usage:
{% include toc_pure_liquid.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %}
Parameters:
* html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
Optional Parameters:
* sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC
* class (string) : '' - a CSS class assigned to the TOC
* id (string) : '' - an ID to assigned to the TOC
* h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored
* h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored
Output:
An unordered list representing the table of contents of a markdown block. This snippet will only generate the table of contents and will NOT output the markdown given to it
{% endcomment %}
{% capture my_toc %}{% endcapture %}
{% assign minHeader = include.h_min | default: 1 %}
{% assign maxHeader = include.h_max | default: 6 %}
{% assign nodes = include.html | split: '<h' %}
{% for node in nodes %}
{% if node == "" %}
{% continue %}
{% endif %}
{% assign headerLevel = node | replace: '"', '' | slice: 0, 1 %}
{% assign headerLevel = headerLevel | times: 1 %}
{% assign indentAmount = headerLevel | minus: minHeader | add: 1 %}
{% assign _workspace = node | split: '</h' %}
{% unless headerLevel >= minHeader %}
{% continue %}
{% endunless %}
{% if headerLevel > maxHeader %}
{% continue %}
{% endif %}
{% assign _idWorkspace = _workspace[0] | split: '"' %}
{% assign html_id = _idWorkspace[1] %}
{% capture _hAttrToStrip %}{{ headerLevel }} id="{{ html_id }}">{% endcapture %}
{% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
{% assign space = '' %}
{% for i in (1..indentAmount) %}
{% assign space = space | prepend: ' ' %}
{% endfor %}
{% capture my_toc %}{{ my_toc }}
{{ space }}- [{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}](#{{ html_id }}){% endcapture %}
{% endfor %}
{% if include.class %}
{% capture my_toc %}{:.{{ include.class }}}
{{ my_toc | lstrip }}{% endcapture %}
{% endif %}
{% if include.id %}
{% capture my_toc %}{: #{{ include.id }}}
{{ my_toc | lstrip }}{% endcapture %}
{% endif %}
{% endcapture %}{% assign tocWorkspace = '' %}
{{ my_toc | markdownify }}
In theory it should work that way, (it is not working for me either) but you can force to process the code inside a block with `markdown="1" like this:
<aside markdown="1">
<h4>Table of Contents</h4>
* ToC
{:toc}
</aside>
Make sure you don't indent the code inside the aside tag or it will be parsed as kramdown code.
By default, kramdown parses all block HTML tags and all XML tags as
raw HTML blocks. However, this can be configured with the
parse_block_html. If this is set to true, then syntax parsing in HTML
blocks is globally enabled. It is also possible to enable/disable
syntax parsing on a tag per tag basis using the markdown attribute:
If an HTML tag has an attribute markdown="0", then the tag is parsed as raw HTML block.
If an HTML tag has an attribute markdown="1", then the default mechanism for parsing syntax in this tag is used.
Update
I've checked your repo, you need to rename index.html to index.md so kramdown will parse it and then you can also add the line to _config.yml to parse markdown inside html blocks.
Jekyll only parse .md or .mardown files.
.html files are not proccessed by markdown parser.
If you rename a file from .html to .md, it will be processed as kramdown.
But there you will have problems with indentation.
Mixing html and markdown is not so easy ;-)

Jekyll: Get number of files in front matter specified sub-directories?

Imagine a directory specified in pages' front matter:
---
...
assets: "/assets/<project-name>"
...
---
How can one determine the number of files in the assets' sub-directories? For example:
{% assign img_dir = page.assets | append: "img/" %}
{% if <files-in-img_dir>.length > 1 %}
// Render multiple images
{% else %}
// Render single images
{% end if %}
As of right now I'm using something like below, but I would like to be able to optimize (imports, build, etc.) based on the number of assets to render.
{% for asset in site.static_files %}
{% if asset.path contains img_dir %}
// Render
{% endif %}
{% endfor %}
Thanks.

List Subcategories in GitHub Pages

Edit: I've created a repository here that tests jibe's answer below. I just end up getting a blank page when I visit /animals, so any help is appreciated!
This is a follow-up to this question: Hierarchical Categories in GitHub Pages
In that question, I found out how to list the posts of a particular hierarchical category. Now I'm trying to figure out how to list the subcategories of a particular hierarchical category.
I'm using Jekyll on GitHub pages, and I want to have hierarchical categories like this:
animals -> mammals -> cats -> _posts -> housecat.md, tiger.md
animals -> mammals -> dogs -> _posts -> poodle.md, doberman.md
animals -> reptiles -> lizards -> _posts -> iguana.md, chameleon.md
I'd like users to be able to visit /animals and see a listing of the subcategories (mammals and reptiles). Then if they go to /animals/mammals, they'd see cats and dogs listed as subcategories.
I'm currently doing this manually by putting an index.html file inside each subcategory. But that makes updating things much more complicated than it probably should be.
I've tried following this answer, but that's meant for single tags, not multiple categories.
The catch is that any particular category might not be unique, so I can have stuff like this:
animals -> mammals -> bats -> _posts -> vampire.md, fruit.md
sports -> baseball -> bats -> _posts -> wiffle.md, teeball.md
I'd also like to be able to define frontmatter attributes in the subcategories, maybe in the index.html file of each? For example the animals->mammals->bats->index.html file would contain a frontmatter variable thumbnail with a value of "VampireBat.jpg", and sports->baseball->bats->index.html would have a thumbnail of "YellowWiffleBat.png". I'd like to be able to access these variables from the parent level (to show a thumbnail and a link to the subcategory).
My first thought was to access the subcategories directly, like this:
{% for mammalType in site.categories.animals.mammals %}
<p>{{ mammalType.title }}</p>
<img src="(( mammalType.thumbnail }}" />
{% endfor %}
Which I'd generalize using the categories from the page itself:
{% for subcategory in page.categories %}
<p>{{ subcategory.title }}</p>
<img src="(( subcategory.thumbnail }}" />
{% endfor %}
But that doesn't work at all, since site.categories.whatever is a list of all of the posts in that category, ignoring any hierarchical information.
Is there a better way to approach this other than doing it manually?
See simpyll.com for demo
See github for website code
assign var page_depth as the current pages depth using the path '/' as a count variable
{% assign page_depth = page.url | split: '/' | size %}
assign var page_parent as the slug of the last directory housing 'index.md'
{% assign page_parent = page.url | split: '/' | last %}
loop through every page in the website
{% for node in site.pages offset:1 %}
skip website root
{% if node.url == '/' %}
{{ continue }}
{% else %}
remove backslashed from each page in website
{% assign split_path = node.url | split: "/" %}
assign var node_last for each page in website
{% assign node_last = split_path | last %}
assign var node_parent as the slug of the last directory housing 'index.md' for each page in website
{% assign node_parent = node.url | remove: node_last | split: '/' | last %}
assign node_url for each page in website
{% assign node_url = node.url %}
loop through each slug in each page path in website
{% for slug in split_path offset:1 %}
assign var slug as the name of each slug therefore giving it a name
{% assign slug = slug %}
assign slug_depth with a forloop.index
{% assign slug_depth = forloop.index %}
close for
{% endfor %}
obtain sub-directories for every page in website comparing depth and parent of current page to that of every other page in website
{% if slug_depth == page_depth and page_parent == node_parent %}<li>{{ slug }}</li>{% endif %}
obtain sub-directories for root (which we skipped early in this script). we can use depth alone to define this.
{% if slug_depth == 1 and page.url == '/' and slug != 'search.json' and slug != 'sitemap.xml' %}<li>{{{slug}}</li>{% endif %}
close if and for
{% endif %}
{% endfor %}
altogether:
{% assign page_depth = page.url | split: '/' | size %}
{% assign page_parent = page.url | split: '/' | last %}
{% for node in site.pages offset:1 %}
{% if node.url == '/' %}
{{ continue }}
{% else %}
{% assign split_path = node.url | split: "/" %}
{% assign node_last = split_path | last %}
{% assign node_parent = node.url | remove: node_last | split: '/' | last %}
{% assign node_url = node.url %}
{% for slug in split_path offset:1 %}
{% assign slug = slug %}
{% assign slug_depth = forloop.index %}
{% endfor %}
{% if slug_depth == page_depth and page_parent == node_parent %}
<li>{{ slug }}</li>
{% endif %}
{% if slug_depth == 1 and page.url == '/' and slug != 'search.json' and slug != 'sitemap.xml' %}
<li>{{{slug}}</li>
{% endif %}
{% endif %}
{% endfor %}
As it was suggested in my deleted answer, I post an improved version of my answer of your previous question. I also add information to answer your new questions (also deleted) :
Thanks for the reply. This almost works, but it's got a few problems. Most importantly, it doesn't support subcategories with the same name (like animals->bats and baseball->bats). It also lists every subcategory and every post under a particular category. I only want to list the subcategories, not the posts. Is there a way to modify your approach to meet those requirements? – Kevin Workman yesterday
Modify your _config.yml accordingly
collections:
animals:
output: true
mammals:
output: true
cats:
output: true
dogs:
output: true
reptiles:
output: true
lizards:
output: true
then created the structure:
mkdir -p _animals/reptiles/lizards
mkdir -p _animals/mammals/cats
mkdir _animals/mammals/dogs
add your md files and all index.html you need to make the list you want.
which will index items with filter. From the top directory, your animals collection should look like this (with index.html in each folder) :
cleaner
root/
└── _animals/
├── index.html
├── mammals
│ ├── cats
│ │ ├── housecat.md
│ │ └── tiger.md
│ ├── dogs
│ │ ├── doberman.md
│ │ └── poodle.md
│ └── index.html
└── reptiles
└── lizards
├── chameleon.md
└── iguana.md
new you can list only subcategories with or without going deeply (with an optional parameters) _includes/list_subcategories.html
{% assign animals = site.animals| sort:'title' %}
{% assign from = page.url | remove: '/index.html' %}
{% assign deep = (page.url | split: '/' | size) + 1 %}
{% for animal in animals %}
{% assign d = animal.url | remove: '/index.html' | split: '/' | size %}
{% if animal.url != page.url and animal.url contains from and animal.url contains "index" and (include.dig or deep == d) %}
<a href={{ animal.url | prepend: site.baseurl }}>{{animal.title}}</a>
{% endif %}
{% endfor %}
improved similarly to list animals _includes/list_animals.html
{% assign animals = site.animals| sort:'title' %}
{% assign from = page.url | remove: '/index.html' %}
{% assign deep = (page.url | split: '/' | size) + 1 %}
{% for animal in animals %}
{% assign d = animal.url | remove: '/index.html' | split: '/' | size %}
{% if animal.url contains "index" or animal.url == page.url %}
{% else %}
{% if animal.url contains from and (include.dig or deep == d) %}
<a href={{ animal.url | prepend: site.baseurl }}>{{animal.title}}</a>
{% endif %}
{% endif %}
{% endfor %}
list all subcategories and all animals in animals/index.html :
---
title: animals
---
{% include list_subcategories.html dig=true %}
<hr>
{% include list_animals.html dig=true %}
For example, to list all mammals and subcategoeries in animals/mammals/index.html :
---
title: animals
---
{% include list_subcategories.html %}
<hr>
{% include list_animals.html %}
Finally the generated structure should look like this (with some more index.html):
cleaner
root/
├─ _animals/
│ └─── ...
└── _site
└── animals
├── index.html
├── mammals
│ ├── cats
│ │ ├── housecat.html
│ │ └── tiger.html
│ ├── dogs
│ │ ├── doberman.html
│ │ └── poodle.html
│ └── index.html
└── reptiles
└── lizards
├── chameleon.html
└── iguana.html
it solves your question. I changed from taxonomy to dig, but you could also have distinguished between animals->bats and baseball->bats by putting taxonomy="baseball/bats" or taxonomy="animals/bats".

Jekyll linked documents in collections?

In Jekyll's Front Matter, is there a way to make references to another document?
I have a custom collection, and would like to add meta-data in each document such as "parent-topic" (a link to the parent), and "children" (an array of documents), or "related-topics".
With such a reference I could access the linked documents' meta-data, such as their title, url, or other arbitrary data.
The idea is a hierarchy of documentation, with topics, sub-topics, sub-sub-topics, etc. And a topic page could show a list of child topics, or a breadcrumb for the parent topics, etc.
Real question that deserve a real answer. I also got this documentation problem. Following advise from Ben Balter, I started to use collections. The idea was to make
a table of content reflecting topic/sub-topics arrangement,
a breadcrumb on each page
I gave up because it was simplest to code against pages. So, here's how I do documentation with pages.
Prerequisites :
documentation is in a folder eg : documentation
permalink is set to pretty in _config.yml
folders hierarchy describes documentation organization
example
documentation
|--index.html
|--chapter-1
| |--index.html
|
|--chapter-2
| |--index.html
| |
| |--part-1
| | |--index.html
| | |--subpart-1
| | |--index.html
| |--part-2
| | |--index.html
| |
| |--part-3.html
Note : documentation/chapter-2/part-2/index.html can also be documentation/chapter-2/part-2.html, because permalink is set to pretty, generated page will be at documentation/chapter-2/part-2/index.html.
Pages at a same level are sorted depending on a weight front matter variable. This can be anything you want.
Numbering by tenth allows easy insertion for new doc.
example front matter
---
title: My title
weight: 10
---
documentation get default variables values from _config.yml
example
defaults:
-
scope:
path: "documentation"
type: pages
values:
isDoc: true # allows quick extraction from site.pages
layout: page
Once those prerequisites are in place, it's easy to print a table of content and a breadcrumb.
Table of content
_includes/show-children.html
{% assign parentDir = include.dir %}
{% if parentDir == nil %}<h1>You must specify a root directory</h1>{% endif %}
{% assign allDocs = include.docs %}
{% if allDocs == nil %}{% assign allDocs = site.pages | sort: "weight" %}{% endif %}
{% assign level = include.level %}
{% if level == nil %}{% assign level = parentDir | remove_first: "/" | split:"/" | size %}{% endif %}
{% assign maxLevel = include.maxLevel %}
{% if maxLevel == nil %}{% assign maxLevel = 100 %}{% endif %}
{% assign nextLevel = level | plus : 1 %}
{% comment %}+++++++++++++++++++++++++++++++++++++++++++++++++
Looking for all page in this path with the same level (siblings)
This avoid to deep recursion and error like :
__ Liquid Exception: Nesting too deep __
+++++++++++++++++++++++++++++++++++++++++++++++++{% endcomment %}
{% assign siblings = "" | split: "/" %}
{% for s in allDocs %}
{% assign sPageLevel = s.url | remove_first: "/" | split:"/" | size %}
{% if sPageLevel == level and s.url contains parentDir %}
{% if s.title %}{% assign siblings = siblings | push: s %}{% endif %}
{% endif %}
{% endfor %}
<ul>
{% for p in siblings %}
<li><a href="{{site.baseurl}}{{p.url}}"{%if p.url == page.url%} class="active"{%endif%}>{{ p.title }}</a>
{% if nextLevel <= maxLevel %}
{% include show-children.html dir=p.dir docs=allDocs level=nextLevel maxLevel=maxLevel %}
{% endif %}
</li>
{% endfor %}
</ul>
{% comment %}+++++++++++++++++++++++++++++++++++++++++++++++++
Because all variables are globales (all includes have the same scope)
we restore level and nextLevel variables to parent values
+++++++++++++++++++++++++++++++++++++++++++++++++{% endcomment %}
{% assign level = level | minus : 1 %}
{% assign nextLevel = nextLevel | minus : 1 %}
Use
This include can be called with several arguments :
dir : root dir to explore (ie : /documentation)
docs : an array of pages - default to site.pages
level: level at which we start printing (/documentation is at level 1,
/documentation/chapter-1 is at level 2, and so on)
Default to 'dir' level
maxLevel: where to stop to print - default to 100
Extracting documentation pages
{% assign documents = site.pages | where: "isDoc", true | sort: "weight" %}
{% assign dir = "documentation" %}
This will print all documentation hierachy
{% include show-children.html dir=dir docs=documents %}
This will start printing at level 2
{% include show-children.html dir=dir docs=documents level=2 %}
This stop printing at level 2
{% include show-children.html dir=dir docs=documents maxLevel=2 %}
On page layout if you just want to print page children you can do :
{% assign documents = site.pages | where: "isDoc", true | sort: "weight" %}
{% assign level = page.dir | remove_first: "/" | split:"/" | size %}
{% assign childrenLevel = level | plus : 1 %}
{% include show-children.html docs=documents dir=page.dir level=childrenLevel %}
Breadcrumb
_includes/breadcrumb.html
{% assign minLevel = include.minLevel %}
{% if minLevel == nil %}{% assign minLevel = 1 %}{% endif %}
<div class="breadcrumb">
<p>You are here : </p>
{% assign documents = site.pages | where: "isDoc", true | sort: "weight" %}
{% include get-parents.html page=page minLevel=minLevel docs=documents %}
<p>{{ page.title }}</p>
</div>
<style type="text/css">
.breadcrumb p { display: inline; }
.breadcrumb p+p+p:before { content:"» "; }
</style>
_includes/get-parents.html
{% assign currentPage = include.page %}
{% assign minLevel = include.minLevel %}
{% assign allDocs = include.docs %}
{% assign pageLevel = currentPage.dir | remove_first: "/" | split:"/" | size %}
{% assign parentLevel = pageLevel | minus: 1 %}
{% if parentLevel >= minLevel %}
{% for p in allDocs %}
{% assign pPageLevel = p.dir | remove_first: "/" | split:"/" | size %}
{% if pPageLevel == parentLevel and currentPage.dir contains p.dir %}
{% include get-parents.html page=p minLevel=minLevel docs=allDocs %}
<p>{{ p.title }}</p>
{% endif %}
{% endfor %}
{% endif %}
Use
Print Documentation > chapter 1 > part 1
{% include breadcrumb.html %}
Print Chapter 1 > part 1
{% include breadcrumb.html minLevel=2 %}
Can it be more simple ?
Working code can be found here.