Why does Jekyll process one collection but not another? - yaml

The problem
I am setting up a Jekyll site which includes collections for categories and authors. I had the categories collection working after some trial & error, now it's broken again but the authors collection works, and I don't understand why. I think something in my configuration is wrong, maybe the _config.yml, but there's no error messages. I could see sometimes when I create a new category (say, _categories/Science), the processor immediately creates a corresponding entry in _site/categories. Categories collection was working perfectly but then something broke, I went back to another commit and now the situation is reversed (author collection works, categories doesn't).
This is my whole _config.yml:
include: ['_categories']
include: ['_authors']
defaults:
-
scope:
path: "" # matches all files in the project
type: posts
values:
layout: "blogLayout"
collections:
category:
output: true
permalink: /categories/:path/
layout: "category"
author:
output: true
permalink: /authors/:slug/
layout: "author"
The collection files are all really simple stubs. This is what a category looks like (_categories/Science.md):
---
layout: category
category-name: Science
permalink: /categories/Science/
---
and here's from the author collection:
---
author: Leo Tolstoy
permalink: /authors/leo-tolstoy/
layout: author
---
My hunch is that it's probably a syntax thing in the _config.yml, either in the YAML syntax or I'm missing up a singular/plural (e.g. authors vs author) someplace but I'm not sure where to look. For completeness sake here's the Front Matter from a post, all of which has worked great until now.
---
layout: blogLayout
title: Anna Karenina excerpt
author: Leo Tolstoy
categories: [testing,Literature]
location: Russia
---
Update (I got it working)
After a bunch of trial & error I eventually got it up and running and figured I would summarize for others who are trying to set up a Jekyll taxonomy.
My final _config.yml file was very simple:
collections:
categories:
output: true
permalink: categories/:path/
authors:
output: true
permalink: authors/:path/
output: true is needed for Jekyll to process that collection. I needed permalinks to be able to go to a directory URL like /categories/dogs/ instead of /categories/dogs.html. It also proved import to NOT have permalinks in any of the collection item files. Live development (serve) mode didn't mind, but when I built the static site it broke a lot of the links until I removed the double permalinks.
The name scheme is that collection items go in _categories/ (or whatever) directory, these don't need much at all. Here is a category file for dogs.md:
---
layout: category
name: Dogs
---
This allowed me to reliably build collections, but searching and viewing a collection took some more work. In this case I had _categories directory to put the dogs.md and such files in. I also needed a categories.html file in the root directory, and a layout called category.html (note the singular vs plural). Here are minimal examples to show what's needed.
categories.html
---
layout: default
title: Categories
---
<h1>Categories</h1>
{% for category in site.categories %}
<h2>{{ category.title }}
</h2>
{% endfor %}
and here is _layouts/category.html:
---
layout: default
---
<h1> {{ page.name }} </h1>
{% assign posts = site.posts | where_exp: "post", "post.categories contains page.name" %}
{% for post in posts %}
<h3>{{ post.title }}</h3>
{% endfor %}
A tricky detail is that other fields types have a field.name property but categories alone have category.title instead.

Related

Cannot access liquid object properties (Jekyll)

I am new to Jekyll and am creating a documentation website for one of my projects. I am trying to create a sidebar that displays the current page in the documentation. To store the structure of the documentation, I created the a file in the _data folder called subsections.yml. Here is the file:
- title: Quickstart # Section
data:
- Get started # Subsections
- The basics
- title: API documentation # Another section with subsections
data:
- Introduction
Here is an excerpt from the html template file that will be used for pages in the documentation. (liquid template engine):
{% assign subsecs = site.data.subsections | where: 'title', page.section %}
The code above creates a variable called subsecs which is created by reading subsections.yml and filtering out the data on the section the documentation page is about. So if the page's section was Quickstart, the subsecs variable would contain all the data from the Quickstart section from subsecitons.yml. I tested this out with {{ subsecs }} and it worked by outputting:
{"title"=>"Quickstart", "data"=>["Get started", "The basics"]}
However, when I try to access a certain property from this object like title:
{{ subsecs.title }}
nothing is returned. Why is this happening, and how can I access property methods in liquid? The syntax looks correct, bu when I try it, an empty string is rendered.
I tried looking at the liquid documentation, but found nothing other than method.property, which doesn't work for some reason. I also looked at similar SO questions.
The where filter is returning an array.
{% assign subsecs = site.data.subsections | where: 'title', page.section %}
{{ subsecs | inspect }}
inspect filter prints => [{"title"=>"Quickstart", "data"=>["Get started", "The basics"]}] brackets denotes an array.
You can do :
{% assign subsecs = site.data.subsections | where: 'title', page.section | first %}
{{ subsecs | inspect }}
The first filter extracts the first element of your array.
inspect filter now prints => {"title"=>"Quickstart", "data"=>["Get started", "The basics"]}
You can now access your object's properties like subsecs.title.

Overriding a context register in a Jekyll Block plugin

I'm trying to get more control over my post excerpts in Jekyll. Using the default excerpt code doesn't seem to work well in my case, as it (a) needs to be the first paragraph, and (b) is still rendered on the page. For most of my blog posts, I want either custom text, which isn't meant to be part of the actual blog post, or some text later on in the blog post. I was hoping to do this with a block, that can optionally take a parameter to render the content (although I'll add in that specific feature later). I've currently got this proof-of-concept plugin:
require "jekyll"
module Jekyll
class RenderExcerptBlock < Liquid::Block
def render(context)
page = context.registers[:page]
content = super
page["excerpt"] = content.lines[0..50]
content
end
end
end
Liquid::Template.register_tag "excerpt", Jekyll::RenderExcerptBlock
Which would theoretically let me do this:
{% excerpt %}
A snippet of this post
{% endexcerpt %}
However, this gives the error when running jekyll build:
Liquid Exception: Key excerpt cannot be set in the drop. in /Users/nick.chambers/blog/_posts/2020-01-21-unix-shell-scripting-with-bash.md
ERROR: YOUR SITE COULD NOT BE BUILT:
------------------------------------
Key excerpt cannot be set in the drop.
Is it possible to override this register's value in some way? Alternatively, is there a more Jekyll way to achieve this?
The Jekyll way to have a custom excerpt for your post (or any document in a collection), is to simply add it to the front matter:
---
title: Test Doc
excerpt: This is a custom excerpt
---
The opening paragraph of this document.
Another paragraph in this document.
Then reference it in your layout to render it:
<div class="post-excerpt">
{{ page.excerpt }}
</div>
or
<div>
{% for post in site.posts %}
<h2>{{ post.title }}</h2>
<div>{{ post.excerpt }}</div>
{% endfor %}
</div>

Is there a way in Jekyll to retrieve the number of pictures in a post's content?

In a Jekyll's post I've seen how to count words with include.content | number_of_words but I was wondering if there was a way to count pictures in the content?
I do know there is a way to get a featured image if I add it to the frontmatter like:
feature-img: "img/foobar.png"
Per my searches I did see post.layout but I am unable to find if there is a way to get the count of images in a post's content. When I search to see if this has been asked or someone has brought this up in Jekyll issues the only I do not get any results but I have read:
Is there a way to access rendered content in Jekyll?
How to retrieve the current post index number in Jekyll?
How to include all files in a folder in Jekyll?
I could see if I was going to build a gallery for a post adding the images to the frontmatter like:
---
layout: post
title: "This is a title"
images:
- url: "img/foo.png"
alt: "Enter the foo"
title: "apart of foo"
- url: "img/bar.png"
alt: "Enter the bar"
title: "apart of bar"
---
but the images are spread throughout the content. I guess I could hard code it to every post in the frontmatter like:
---
image-count: 4
---
but I think that just bloats the frontmatter. In Jekyll is there a way to get the post's images count dynamically?
You can probably split the content, like this:
{% assign contentparts = content | split: '<img' %}
{% assing amountofimages = contentparts | minus:1 %}
Then use:
{{ amountofimages }}
Note that I did not test this.
I've figured out a way to get the count of images in a post without hard coding the image count in the front matter:
{% assign postPics = include.content | split: "<img" | size %}

Jekyll tags that have spaces or multiple words

I'm using tags in Jekyll for my blog posts. The following is an example of tags I declare in the front matter in the Markdown file:
---
tags: [jekyll, tags, user experience]
---
The problem I have is that the "user experience" is rendered with a space, which breaks the link for the tag. I'd like to know if it's possible to have tags that have spaces or multiple words.
This is what my code looks like:
Markup with Ruby:
{% if page.tags.size > 0 %}
<div class="post-tags">
<ul>
{% for tag in page.tags %}
<li>{{ tag }}{% if forloop.last == false %},{% endif %}</li>
{% endfor %}
</ul>
</div>
{% endif %}
Does anyone have any ideas on how I can do that? Thanks!
You can use url_encode filter : {{ tag | url_encode }}.
Note that url_encode will turn a space into a + sign instead of a percent-encoded character. cf. Liquid doc
I had the same problem when I created the tag pages in my blog.
My solution was simply to replace the blanks by dashes:
---
tags: [jekyll, tags, user-experience]
---
Example front-matter from one of my posts:
tags:
- backup
- bitbucket-backup
- roboshell-backup
- source-control
The finished HTML looks like this:
<p><small>tags:
<span class="blog_post_categories">
backup,
bitbucket-backup,
roboshell-backup,
source-control
</span>
</small></p>
I'm sure that there is a more elegant solution which displays the blanks actually as blanks, but for me, dashes were good enough.
I discovered a fix. When you have spaces in the tags, e.g., "user experience", Jekyll isn't concatenating the words as part of the rendered link, leaving the space between both words: your.site/tags/user experience
I needed the two words joined by a + because my Tags page rendered a link to that section as #user+experience. So I added replace like this:
{{ tag | replace: " ","+" }}
This still feels like a bug in Jekyll, but this process works. Replace the space with whatever syntax you need.

Get a hash into a Jekyll Liquid template from a Plugin for use in the FOR loop?

This one's got me stumped...
I'd like to share a YAML hash from a single file among a few other Jekyll pages.
I know you can put it in the Front Matter (which would require duplicating it), and I know you can generate (write) pages via a plugin (but I'm using it in a few different types of pages, which would be complex). Neither is what I'm looking for.
I'd like to loop over the hash with Liquid in my pages, but I can't seem to get the hash from the plugin to Liquid. {% capture %} only works with strings and {% assign %} won't let you call a tag within itself, like {% assign projects = gethash %} where gethash is a custom Liquid tag.
Basically, I'd like to use the separate YAML file like a text-based database.
YAML File has this in it:
projects:
category1:
-
title: Project 1
desc: Description
etc...
-
title: Project 2
etc...
category2:
-
title: Project 3
desc: Description
etc...
-
title: Project 4
etc...
Plugin is calling (which gives a Ruby Hash of the YAML):
def...
YAML::load(File.read('projects.yml'))
end...
And in template, I want to:
{% for p in projects %}
...
This should be really simple, but it's one of those Liquid things that is a pain.
How can you get a hash into Liquid from a plugin for use in the {% for %} loop?
Here's the solution that I came up:
A Jekyll Plugin that create's a Liquid Tag: yaml_to_liquid. This plugin parses the yaml file into a hash and then adds it to the Jekyll page variable.
module Jekyll
class YamlToLiquid < Liquid::Tag
def initialize(tag_name, arg, tokens)
super
if arg.length == 0
raise 'Please enter a yaml file path'
else
#yml_path = arg
end
end
def render(context)
yml = YAML::load(File.read(#yml_path))
context.registers[:page]['yml'] = yml
end
end
end
Liquid::Template.register_tag('yaml_to_liquid', Jekyll::YamlToLiquid)
To use it. Place the tag at the top of your .html or .md page just below the Yaml Front Matter and then access the yml variable as usual. This loop will only output the code hash (allows you to access the whole hash or just sub-hashes):
---
layout: page
---
{% yaml_to_liquid work/_projects.yml %}
<ul>
{% for n in page.yml.projects.code %}
<li>
{{ n.title }}
</li>
{% endfor %}
</ul>
Example of work/_projects.yml:
projects:
code:
- title:
url:
- title:
url:
websites:
- title:
url:
- title:
url:
Well, if you don't need it to be a plugin, it can be put in your _config.yml. For a plugin, you might need to append the hash to the site variable.
I think that a generator would suffice. There is a page about plugins that you should take a look.
This is what I'd use (I can't test right now, so it might be wrong!):
module Jekyll
class ProjectsGenerator < Generator
safe true
def generate(site)
# This probably won't work.
site.projects = YAML::load(File.read('projects.yml'))
end
end
end
Anyway, I really think that if you don't need the extra complexity (having a separate file, creating a new plugin just for you, etc), just put the data in _config.yml. Simplicity is good.
Hope that helps. :)

Resources