How to use boolean negation in "where:"? - jekyll

I am trying to write a query to retrieve posts of all categories but the category "Personal".
I have the following code to retrieve all posts which are tagged as "Personal", so I tried to use " ! " and " not " to get which are not "Personal".
{% assign sorted-posts = site.posts | where: "categories", "Personal" %}
...
{% endfor %}
So, I tried this:
{% assign sorted-posts = site.posts | not where: "categories", "Personal" %}
...
{% endfor %}
and
{% assign sorted-posts = site.posts | ! where: "categories", "Personal" %}
...
{% endfor %}
It didn't work. How can I use " not " or " ! " in Liquid?

As post.categories is an array, you can check if it contains a certain value with contains filter. eg : if post.categories contains "Personal"
The unless control flow tag, which is equivalent to "if not" can now be used to print all posts that are not from "personal" category.
<ul>
{% for post in site.posts %}
{% unless post.categories contains "Personal" %}
<li>{{ post.title }}</li>
{% endunless %}
{% endfor %}
</ul>

Related

Liquid/Jekyll: How to check for no posts when one has two or more conditions?

I am having trouble understanding how I might show a "no posts exist" message for a particular conditional statement with two variables.
In this example, let's say I have a collection of "animals" - on a particular page, I'd like a section that displays "primates that are herbivores":
{% for animal in site.animal %}
{% if animal.species == "primate" and animal.type == "herbivore" %}
{{ animal.content }}
{% endif %}
{% endfor %}
What I'd like to do is something like this (pseudocode):
{% if POSTS_EXIST_FOR_THIS_COMBO: (animal.species == "primate" and animal.type == "herbivore") %}
{% for animal in site.animal %}
{% if animal.species == "primate" and animal.type == "herbivore" %}
{{ animal.content }}
{% endif %}
{% endfor %}
{% else %}
There are no posts for this category.
{% endif %}
Note: This differs from examples like this, because I have two parameters to check. Can someone offer a suggestion about the syntax?
I think you can do the following where you at first filter all by species=primate from site.animal and then filter by type=herbivore from that pool and then check if the result exists.
{% assign animals = site.animal | where:"species","primate" | where:"type","herbivore" %}
{% if animals %}
{% for animal in animals %}
{{ animal.content }}
{% endfor %}
{% endif %}
Hope this helps.

Jekyll: Listing all categories but one with a limit breaks the limit

I am trying to output a list of post titles with the 5 latest posts in ALL categories BUT one. It works fine with the filtering, unfortunately it takes the posts that are being excluded in consideration for the limit. Is there any easy way to not count the excluded posts?
{% for post in site.posts limit: 5 %}
{% if post.category == "news" %}
//Do nothing
{% else if %}
{{ post.title }}
{% endif %}
{% endfor %}
To limit filtered posts you need to filter them first.
We create an array of posts that do not include the news category:
{% assign no_news = "" | split: "" %}
{% for post in site.posts %}
{% unless post.categories contains 'news' %}
{% assign no_news = no_news | push: post %}
{% endunless %}
{% endfor %}
Now we traverse this new array limiting iterations by 5:
{% for post in no_news limit: 5 %}
{{ post.title }} - {{post.categories}}
{% endfor %}
It will output 5 posts not containing the news category.
You should be able to assign the posts first, then call them:
{% assign relevant = site.posts | except:"category","news" %}
{% for posts in relevant limit:5 %}
{{ post.title }}
{% endfor %}
For more: https://github.com/jekyll/jekyll/issues/2018

Looping through all list of posts not in some category in liquid

I want to loop through posts on a site except ones with the category unlisted. I'm able to do this by nesting an if statement inside the for loop, but this breaks down when I want to also specify a limit – the loop will run for 5 times only regardless of whether the post passes the check.
{% for post in site.posts limit: 5 %}
{% unless post.categories contains 'unlisted' %}
<!-- display post -->
{% endunless %}
{% endfor %}
I need to pass an already filtered list to the for loop, but I'm unable to do this mainly because I can't find a way to combine the where filter with contains and negation:
{% for post in site.posts | WHERE CATEGORIES NOT CONTAINS 'UNLISTED' | limit: 5 %}
<!-- display post -->
{% endfor %}
You can use a counter :
<ul>
{% assign postCounter = 0 %}
{% assign maxPost = 5 %}
{% for post in site.posts %}
{% unless post.categories contains 'unlisted' %}
<li>{{ post.title }}</li>
{% assign postCounter = postCounter | plus: 1 %}
{% if postCounter >= maxPost %}
{% break %}
{% endif %}
{% endunless %}
{% endfor %}
</ul>

Sorting/grouping collections for an archive

I am building my first site and am having a very difficult time grouping/sorting collections. The only success I have had is a list of files sorted alphabetically by subdirectory, then by files by their dates (seems to be the default) using:
<ul>
{% for page in site.collection_name | sort: weight %}
<h3>{{ page.title }}</h3>{{ page.category }}{{page.excerpt}}
{% endfor %}
</ul>
Weight (above) does not work. Neither did type, category, or any other variable I substituted.
My files are in subdirectories, and have permalinks as:
/collection_name/category_name/file_name/
Front matter includes:
title,
category,
layout,
type,
(tried several others)
The collection will have several file types such as:
articles,
videos,
research
What I want to accomplish is something that loops through my collection first for category, then by type. In very bad pseudo code:
<ul>
{% for page in site.{{category_name}} %}
<li><h2>{{category01}}</h2></li>
<ul>
{% for type in site.{{category_name.type}} | sort: date reverse%}
<li><h3>Articles</h3></li>
<li>{{ page.title }}{{page.excerpt}}</li>
<li><h3>Videos</h3></li>
<li>{{ page.title }}{{page.excerpt}}</li>
<li><h3>Research</h3></li>
<li>{{ page.title }}{{page.excerpt}}</li>
</uL>
<li><h2>{{category02}}</h2></li>
. . .
. . . {% endfor %}
Any help or direction will be appreciated.
Try to use group_by :
{% assign byCategory = site.collection_name | group_by: 'category' | sort: 'name' %}
{% for cat in byCategory %}
<h2>{{ cat.name | capitalize }}</h2>
{% assign byType = cat.items | group_by: 'type' %}
{% for type in byType %}
<h3>{{ type.name | capitalize }}</h3>
<ul>
{% for item in type.items %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
Note: this works with category: mycategory not with categories: [one, two]
If you want to specifically order types, you can do :
_config.yml
# this array determine types order
# a collection itm with non matching type will not be listed
types :
- articles
- videos
- research
code
{% assign byCategory = site.area | group_by: 'category' %}
{% for cat in byCategory %}
<h2>{{ cat.name }}</h2>
{% assign byType = cat.items | group_by: 'type' %}
{% for type in site.types %}
{% assign currentType = byType | where:"name", type | first %}
<h3>{{ currentType.name | capitalize }}</h3>
<ul>
{% for item in currentType.items %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}

Jekyll - Get all posts that are in multiple categories

I want to loop over all posts that are assigned category "foo" and category "bar" ..
{% for post in site.categories.foo and in site.categories.bar %}
Is this possible?
In my case "foo" as a "parent" category to "bar" ... /foo/bar/_posts
Thanks
Instead of looking through every post and matching with an or, you can filter by the first tag and then look for the second (and third, fourth, fifth...) tags:
{% for post in site.categories.fizz%}
{% if post.categories contains "buzz" and post.categories contains "bang" %}
<!--post has categories fizz AND buzz AND bang-->
<li>{{ post.title }}</li>
{% endif %}
{% endfor %}
This is a little more efficient than iterating over every single post, and it sets up an and relationship instead of an or relationship.
It is fully possible: loop over all posts, and then select the wanted posts:
{% for post in site.posts %}
{% if post.categories contains "foo" or post.categories contains "bar" %}
<li>{{ post.title }}</li>
{% endif %}
{% endfor %}
Using the Where Expression filter in Jekyll.
Jekyll Liquid Filter Docs
Where Expression,
Select all the objects in an array where the expression is true.
3.2.0
{{ site.members | where_exp:"item", "item.projects contains 'foo'" }}
So on my site I did:
_includes/clippings.html
...
{% capture _filter %}item.tags contains '{{ include.tag }}'{% endcapture %}
{% for clip in site.clippings | where_exp: 'item', _filter %}
{{ clip.do_stuff }}
# more html and stuff
{ endfor }
{% include clippings.html tag='foo' %}
In this case I need to specify the filter tag dynamically. And clippings is just a collection like posts.
If you want to filter by multiple static tags you could do something like:
{% for post in site.posts | where_exp: 'item', "item.tags contains 'foo'" | where_exp: 'item', "item.tags contains 'bar'" %}
{{ post.do_stuff }}
{ endfor }
If you want to do multiple dynamic filter tags then you will need to do something similar to the capture stuff I did above.
I have not tested this, but it should filter posts by an arbitrary amount of filter tags.
{% assign posts = site.posts %}
{% filter_tags = 'foo, bar, buzz' | slipt: ', ' %}
{% for tag in filter_tags %}
{% capture _filter %}item.tags contains '{{ tag }}'{% endcapture %}
{% assign posts = posts | where_exp: 'items', _filter %}
{% endfor %}
{% for post in posts %}
{{ post.do_stuff }}
{% endfor %}
However looping over the whole thing once and checking each post might be more efficient at that point.