Listing all Collections in Jekyll - jekyll

I'm building a magazine site with Jekyll (v2.5.3). The docs on the Jekyll site led me to believe that I could list all the collections on my site, and embed YAML data for each collection in my _config.yml.
_config.yml:
collections:
issue_001:
output: true
permalink: /:title/:path
title: Rebirth
date: 2015-07-01
issue_002:
output: true
permalink: /:title/:path
title: Talking Heads
date: 2015-08-01
index.html:
{% for issue in site.collections %}
<li>
<h6 class="post-meta">Issue {{ issue.name }} — {{ issue.date | date: "%b %-d, %Y" }}</h6>
<h2>
{{ issue.title }}
</h2>
</li>
{% endfor %}
I get two issues appearing on the homepage, but none of the data I'm accessing for each issue (name, date, title etc.) is appearing. I appreciate this is a beta feature, so just wanted to ask is this broken, or am I doing it wrong?

In case for someone still has this request here's how it works in latest Jekyll (v3.8.3):
Update: tested it still works in v4.2.0
{% for collection in site.collections %}
<h2>Items from {{ collection.label }}</h2>
<ul>
{% for item in site[collection.label] %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
{% endfor %}

In {% for issue in site.collections %}, issue is an array that contains :
0 => "issue_001",
1 => Hash
{"output"=>true,
"permalink"=>"/:title/:path",
"title"=>"Rebirth",
"date"=>#,
"label"=>"issue_001",
"docs"=>[#],
"files"=>[],
"directory"=>"/home/djacquel/www/test.dev/jekyll/wat/_issue_001",
"relative_directory"=>"_issue_001"}
The right way to access datas is :
{% for issue in site.collections %}
<li>
<h6 class="post-meta">
Issue {{ issue[1].label }}
—
{{ issue[1].date | date: "%b %-d, %Y" }}
</h6>
<h2>
{{ issue[1].title }}
</h2>
</li>
{% endfor %}

Second first; that answer from #sparanoid is fantastic! And between that and the source code from the default Jekyll Theme (Minima), I was able to bodge some of what follows together.
First second; excellent question and how both are not up-voted more is a bit baffling.
And third of many other related points; I'm not on a identical path as the question's poster but the following code may be of use to readers. My use case was wanting a way of listing the continence of a collection similar to the default Jekyll home layout.
_layouts/named_collection.html
Note the source links are downloadable via, clicking on the Raw link/button then Ctrl s to save as a regular text file, however, these links have been frozen in time so be sure to check the gh-pages branch for updates. And output examples are intended in this case to be just examples of possible content, eg. it could be Lorem strings for all that it matters to demonstrate what's to follow.
---
layout: default
---
{% comment %}
License note from S0AndS0; an editor in this context.
Minima (where the following code is sourced from), is shared under the
MIT Lisense, https://github.com/jekyll/minima/blob/master/LICENSE.txt
Thus any alterations to Minima source code is re-shared under the MIT Lisense
{% endcomment %}
{% capture workspace_collections %}
{% assign collection_name = page.collection_name | default: include.collection_name | default: nil %}
{% assign target_collection = site[collection_name] %}
<div class="home">
{%- if page.title -%}
<h1 class="page-heading">{{ page.title }}</h1>
{%- endif -%}
{{ content }}
{% assign has_output = False %}
{%- if target_collection.size > 0 -%}
{% capture workspace_collection %}
<h2 class="post-list-heading">{{ page.list_title | default: "Posts" }}</h2>
<ul class="post-list">
{%- for post in target_collection -%}
{%- if page.relative_path == post.relative_path -%}
{%- continue -%}
{%- else -%}
{% assign has_output = True %}
{%- endif -%}
<li>
{%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%}
<span class="post-meta">{{ post.date | date: date_format }}</span>
<h3>
<a class="post-link" href="{{ post.url | relative_url }}">
{{ post.title | escape }}
</a>
</h3>
{%- if site.show_excerpts -%}
{{ post.excerpt | markdownify | remove: '<p>' | remove: '</p>' }}
{%- endif -%}
</li>
{%- endfor -%}
</ul>
{% endcapture %}
{%- if has_output == True -%}
{{- workspace_collection -}}{% assign workspace_collection = nil %}
{%- endif -%}
{%- endif -%}
</div>
{% endcapture %}{{ workspace_collections | strip }}{% assign workspace_collections = nil %}
Side note, I can't remember where I picked up that capture some workspace_ trick, but it's pretty sweat for stripping new lines and other formatting shenanigans. If a reader figures out who started such fanciness, please edit or leave a comment.
Usage of this layout is much like the usage of Minima's home.html layout.
administration.md
... Front-Matter selecting from the _administration collection looks like...
layout: named_collection
collection_name: administration
title: Administration
list_title: Usage Examples
permalink: /admin/
... resulting in a <base-url>/admin/ collection listing being output.
git_shell_commands.md
... Front-Matter selecting from the _git_shell_commands collection looks like...
layout: named_collection
collection_name: git_shell_commands
title: Git Shell Commands
list_title: Usage Examples
permalink: /git_shell_commands/
... resulting in a <base-url>/git_shell_commands/ collection listing being output.
Even more notes on collections
If I'm reading the latest Jekyll Collections documentation correctly, as of version 3.7.0 it could be possible to have a custom collections directory, in the case of this question that might look like...
ls ~/git/magazine-name/
# ... other files and/or directories
# issues/
# ... other files and/or directories
# _config.yml
... where the issues/ directory contains sub-directories such as...
ls ~/git/magazine-name/issues/
# _001/
# _002/
# _003/
# ...
... and the _config.yml file has...
collections_dir: issues
collections:
001:
output: True
# ...
... the magic sauce was collections_dir and not using an under-scored prefixed base directory. The documentation was loud about that last bit, eg. _issues/ would make for a bad time.
I'm not sure if there'd be any other adjustments needed to the previously posted Liquid code (I don't think so, but probably); so maybe try'em out individually first.
Warning/Update
Jekyll does not take kindly to numbered collections, eg. _000 will cause errors!
To mitigate such issues be sure to trick Jekyll into treating collection names as strings, eg. _i000
The reason I suggest using a collections_dir, is because in the case of #James Dinsdale's question it may help in organizing things a bit more. Personally I wanted my gh-pages branch collection to mirror some of the same paths as my project's master branch, so didn't pursue testing collections_dir suggestions.
Notes about my Liquid coding choices
Because I'm using the Minima theme I placed the files that used the named_collection theme in my project's root directory; default behavior is to link to such pages at the top of every page. Cheap navigation.
Because readers might place pages that use the named_collection within the same directory as what is being listed, eg...
ls Jekyll_Admin/_administration/
# _administration/administration.md
# ...
... I've written the Liquid for some of the edge-cases, but may not have covered all of'em. Be sure to test privately before unleashing something like it on production.
Updates
After some testing with collections_dir it looks like when used the _posts directory must be moved to that directory too, eg...
_config.yml (Snip)
collections_dir: misc
Bash commands
mkdir misc
mv _posts misc/
... though that's only applicable if using a _posts directory.
Between the time of the last edit and when you're now reading this I've published liquid-utilities/collection-home on GitHub; might be of use to those that want a version tracked solution and is what I'm currently using in any projects that require this feature.

Related

Jekyll how to write posts for different topics

I am very new to jekyll. Currently, I noticed that there is only one /_posts folder in my project. All the posts I wrote in this folder will create a site in /_site/posts.
I wonder is there a way that I can write posts for different topics and generate the sites at different folders? For example, I want to write some posts related to 'travel', so I hope to put the markdown files in a folder like /_travel. I also want to write some posts related to 'work', so I hope to have a folder like /_work. And I hope jekyll can generate these websites separately, like /_site/travel and /_site/work.
The reason I want to solve this is I hope to create a path at the head of my posts. Currently, I am using
{% assign paths = page.url | split: '/' %}
{% for item in paths %}
{% if forloop.first %}
<span>
<a href="{{ '/' | relative_url }}">
{{ site.data.locales[site.lang].tabs.home | capitalize }}
</a>
</span>
{% elsif forloop.last %}
{% if page.collection == 'tabs' %}
<span>{{ site.data.locales[site.lang].tabs[item] | default: page.title }}</span>
{% else %}
<span>{{ page.title }}</span>
{% endif %}
{% elsif %}
<span>
<a href="{{ item | relative_url }}">
{{ site.data.locales[site.lang].tabs[item] | default: page.title }}
</a>
</span>
{% endif %}
{% endfor %}
But apparently, all my work and travel posts are located in the same folder _site/posts
First, you can create your own collections in _config.yml:
collections:
travel:
output: true
permalink: /:collection/:name
work:
output: true
permalink: /:collection/:name
output: true will render a page for each document in the collection.
Then you can create two folders named _travel and _work where you can put your posts.
You access the content of posts in each folder like this:
{% for travel_post in site.travel %}
...
<p>{{ travel_post.content }}</p>
...
{% endfor %}
Lastly, permalink: /:collection/:name should create a link at /travel/name_of_post. See Permalinks for collections for a list of placeholders for the permalink configuration variable.

Adding Jekyll collections prevent posts from rendering

I have a strange problem where adding collections and collections_dir allows me to get one result where I can view the collection results on the ML Projects page that you can see in the sidebar of my website but it prevents any posts in _posts from rendering.
After doing some research I learned that posts is a collection by default, but I'm not sure how this helps me. I tried moving the _posts directory into the _projects directory, which is my collections_dir, but that does not work.
To replicate issue:
Clone the repo at https://github.com/luke-anglin/lukes_site
Build and serve the site, noting that posts do render
Go to _config.yml and remove the comments on line 26-29 which specify the collection and the collections_dir
Rebuild and see that the posts disappear, but the collection things work.
config.yml
# Dependencies
markdown: kramdown
# Permalinks
permalink: pretty
# Setup
title: Luke Anglin
tagline: Computer Science and Engineering Student
description: Software Engineering, DevOps, Data Science
url: http://localhost:4000/
baseurl: /
author:
name: Luke Anglin
# url: https://twitter.com/mdo
plugins:
- jekyll-paginate
paginate: 5
paginate_path: 'page:num'
# Custom vars
# Collections
# collections:
# - ml
# collections_dir: _projects
version: 2.1.0
github:
repo: https://github.com/luke-anglin/lukes_site
defaults:
- scope:
path: 'static/assets/media'
values:
image: true
index.html where the posts are supposed to be looped through
---
layout: default
title: Home
---
<div class="posts">
{% for post in paginator.posts %}
<div class="post">
<h1 class="post-title">
<a href="{{ post.url }}">
{{ post.title }}
</a>
</h1>
<span class="post-date">{{ post.date | date_to_string }}</span>
{{ post.content }}
</div>
{% endfor %}
</div>
<div class="pagination">
{% if paginator.next_page %}
<a class="pagination-item older" href="{{ site.baseurl }}page{{paginator.next_page}}">Older</a>
{% else %}
<span class="pagination-item older">Older</span>
{% endif %}
{% if paginator.previous_page %}
{% if paginator.page == 2 %}
<a class="pagination-item newer" href="{{ site.baseurl }}">Newer</a>
{% else %}
<a class="pagination-item newer" href="{{ site.baseurl }}page{{paginator.previous_page}}">Newer</a>
{% endif %}
{% else %}
<span class="pagination-item newer">Newer</span>
{% endif %}
</div>
Any other info can be found in the repo. Let me know if there are any other questions.
Try to remove the leading '_' from '_projects' directory. The 'collections_dir:' isn't allowed to have this character in the name of the target folder.
See: https://jekyllrb.com/docs/collections/
Particularly the red notice labeled 'Be sure to move drafts and posts into custom collections directory'. At the end it mentions 'Note that, the name of your collections directory cannot start with an underscore (_).'
Hope this helps.

Why does this jekyll template not work?

Short Version:
Why does the following code not produce an output when navbox.next_article is the string '2018-01-05-man-command'?!
{% capture np %} {{ site.posts | where:"post","navbox.next_article contains post.title" }} {% endcapture %}
The next post is {{ np.title }}
Details
My post 2018-01-05-man-command.md has a YAML front matter:
---
layout : post
title : 'Man Command'
tags : [RHCSA, RHCSA_mod, Using Essential Tools, Man Command]
categories: [RHCSA]
navbox:
# prev_article:
next_article: 2018-01-05-understanding-globbing-and-wildcards
---
This is accessed by the _includes/post.html file through:
{% unless include.excerpt %}
{{ post.content }}
{% include navbox.html navbox=page.navbox %}
{% endunless %}
This is used by the _layout/post.html which sets the layout for the post:
{% include post.html post=page link_title=false %}
My navbox.html contains:
{% assign navbox = include.navbox %}
{% capture np %} {{ site.posts | where:"post","navbox.next_article contains post.title" }} {% endcapture %}
The next post is {{ np.title }}
However, all I get when I run bundle exec jekyll serve is:
The next post is
Why does that line not work? I'm new to jekyll so it's possible I've made a blunder somewhere that's intuitive to most. Please tell me what I can fix.
I believe that the capture tag only captures strings, not posts. See here for more info.
I'm not convinced that a where filter supports the contains syntax you're using. See here for more info.
On top of that, where returns an array. You have to get the first item from that array.
You need to fix these issues. Use an assign instead of a capture to store a post. And change your where filter to not use the contains syntax, which isn't valid. (Unless it's been added since the issue I just linked.)
Here is how I've done it:
{% assign post = site.posts | where:"url", targetUrl | first %}

In Jekyll, how do I render custom metadata for a collection?

I have the following in my _config.yml file:
collections:
nr_qa:
output: true
permalink: /:collection/:name
title: 'Node-RED Questions and Answers'
descriptions: 'Node-RED is a flow-based (visual) programming tool. These pages have some information that may be currently missing from the documentaiton.'
github_pages:
title: 'GitHub Pages and Jekyll/Liquid'
description: 'Hints and tips on using Jekyll for publishing to GitHub Pages.'
output: true
permalink: /:collection/:name
and I want to create an automatic index for my collections. So I use code like this:
## {{ site.collections.github_pages.title }}
{{ site.collections.github_pages.description }}
<ul>
{% for item in site.github_pages %}
<li>
{{ item.title | replace:'_',' ' }}
<p>{% if item.description %}
{{ item.description }}
{% else %}
{{ item.excerpt | strip_html }}
{% endif %}</p>
</li>
{% endfor %}
</ul>
And yes, I know I've rather mixed up my markdown and html there. Not relevant to this question.
The problem is that {{ site.collections.github_pages.title }} and {{ site.collections.github_pages.description }} don't render anything even though I think they should.
Can anyone point out my mistake please?
The problem is that title and description should be included in each collection, and not in _config.yml.
Check out Accessing Collection AttributesPermalink for further details.
update
title can be present in each collection metadata in _config.yml. The problem is how you are accessing those variables.
One approach is to have a specific layout for each collection, then you can access them like:
{% assign col = site.collections | where: 'label','github_pages' | first%}.
TITLE: {{ col.title }}.
DESCRIPTION: {{ col.description }}.

Output link to "parent page"?

I have a collection _colletion. In there is a file _collection/path/topic.md and a folder _collection/path/topic/ that includes lots of .md files with content. The permalinks for these files are /path/topic and /path/topic/file-x - so a parent page with a folder with the same name with multiple random pages in it.
Now I want to output a link to /path/topic in all these .md files with the title of topic.md as link text:
---
title: This is the page title defined in topic.md
---
should become
This is the page title defined in topic.md
How do I do that most easily?
Can I somehow access the folder name topic of the .md files and use this to read topic.md and output it's title and also generate a link to it?
My current manual "solution" (or workaround):
Add a parent entry to the frontmatter of all pages in /topic/ that contains the title and relative URL for the topic.md:
parent: ['Topic Title', '../topic']
In the template of the pages:
{% if page.parent %}
<p>« {{ page.parent[0] }}</p>
{% endif %}
Works, but of course duplicates this information n times and has to be maintained manually.
How about this (option 1)?
{% assign pageurl_array = page.url | split: "/" %}
{% assign path = pageurl_array[0] %}
{% assign topic = pageurl_array[1] %}
<p>« <a href="{{ path }}/{{ topic }}/{{ topic }}.html">
{{ topic | capitalize | replace: "-", " " }}
</a></p>
If you do not mind crazy build times, do this (option 2):
{% assign pageurl_array = page.url | split: "/" %}
{% assign path = pageurl_array[0] %}
{% assign topic = pageurl_array[1] %}
{% capture parent_url %}{{ path }}/{{ topic }}/{{ topic }}.html{% endcapture %}
<p>« <a href="{{ parent_url }}">
{% for i in site.pages %}
{% if i.url == parent_url %}
{{ i.title }}
{% endif %}
{% endfor %}
</a></p>
I would go for the first option (much faster) and use this javascript to get the capitals and special characters right:
$('a').each( function() {
var str = $(this).html();
str = str.replace('Topic from url', 'Topic from URL');
$(this).html(str);
});
I admit that the javascript solution is far from pretty, but it solves the build time problem pretty well.
Note that Jekyll is pretty slow. I would advice you to dig into Hugo if you require faster build times.
During discussion in the comments on my question and the other answers I noticed that what I wanted to build was actually a very common thing: A breadcrumb navigation! Just a very "small" one, with only one level.
With this newfound knowledge I could google "breadcrumb" plugins for Jekyll:
This solution uses the path of the page to extract the "crumbs":
https://www.mikestowe.com/blog/2017/08/adding-breadcrumbs-in-jekyll.php
It uses the folder name for the link text.
Another similar implementation:
https://stackoverflow.com/a/9633517/252627
Another one:
https://stackoverflow.com/a/37448941/252627
So no title link text in all of these.
This solution actually reads the page title, but can also read breadcrumb frontmatter from the pages, and uses these as link text:
https://github.com/comsysto/jekyll-breadcrumb-for-github-pages/
https://comsysto.com/blog-post/automatic-breadcrumb-for-jekyll-on-github-pages
https://gist.github.com/csgruenebe/8f7beef9858c1b8625d6
This one might be a valid solution.
There are also real plugins (that unfortunately don't work with Github Pages):
https://github.com/git-no/jekyll-breadcrumbs
My solution, based on JoostS code:
{% assign url = page.url | remove:'.html' | split: "/" %}
{% assign path = url | pop %}
{% if path.size == 1 %}
<a class="back" href="/home/">home</a>
{% else %}
<a class="back" href="/{% for dir in path offset: 1 %}{{ dir | append: "/" }}{% endfor %}">{{ path | last }}</a>
{% endif %}```