Jekyll linked documents in collections? - jekyll

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.

Related

HTML Script For tags is creating extra space

I've been trying to implement an automatic tag with an app for all of my products on Shopify. It works so that the tag created pulls information from the product desciption. If the description states "Pre-order. Ships in 2-3 weeks." or "Pre-order. Ships January 2021." It will pull whatever appears after the the word "Ships in" or Ships". As a result, the tags appear as such:
preorder**2-3 weeks
preorder**January 2021
The tag is used to insert estimated shipping times throughout the site.
The issue right now is that some of the tags have a space after "preorder** " and we need to make sure there is never a space. Any ideas on how to fix? The Body HTML that it is based off is if it contains "Pre-order. Ships in". Here's our current code:
{% assign body = product.body_html | strip_html | newline_to_br %}
{% assign keywords = "Pre-order. Ships" %}
b{% assign tags = '' %}
{% if body contains keywords %}
{% assign lines = body | split: "<br />" %}
{% for line in lines %}
{% if line contains keywords %}
{% assign tokens = line | split: keywords %}
{% if tokens.size == 2 %}
{% assign inner_tokens = tokens[1] | split: "." %}
{% if inner_tokens.size >= 1 %}
{% assign value = inner_tokens[0] | replace: "in", "" | escape_once | replace: " ", "" %}
{% assign tag = "preorder**" | append:value | replace_first: " ", "" %}
{% assign tags = tags | append: tag | append: ',' %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{{ tags }}
Issue
The issue is happening because "Ships in " has one more space than "Ships ".
Solution
You need to remove the space after the "in".
You can do this easily by modifying your replace operator
Code
replace: "in", "" becomes replace: "in ", "" (note the space).
Your code becomes:
{% assign body = product.body_html | strip_html | newline_to_br %}
{% assign keywords = "Pre-order. Ships" %}
b{% assign tags = '' %}
{% if body contains keywords %}
{% assign lines = body | split: "<br />" %}
{% for line in lines %}
{% if line contains keywords %}
{% assign tokens = line | split: keywords %}
{% if tokens.size == 2 %}
{% assign inner_tokens = tokens[1] | split: "." %}
{% if inner_tokens.size >= 1 %}
{% assign value = inner_tokens[0] | replace: "in ", "" | escape_once | replace: " ", "" %}
{% assign tag = "preorder**" | append:value | replace_first: " ", "" %}
{% assign tags = tags | append: tag | append: ',' %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{{ tags }}

Unable to use concat on `nil`

I am trying to build a related post include file for my Jekyll site. The site is based around the concept of members, attractions and parks (each as collections). Each post has a many to many relationships. I am trying to up a combined array of each of the page attributes (members, attractions and parks) loop through the array and find posts with a common number of tags.
It's quite simple but I am getting stuck with one section, not all the posts have members, attractions and parks fields so they are returning nil but the concat filter requires an array. I am trying to default the variable to an [] but it always gets set to nil. Any ideas?
Here's the code:
<ul class="row">
{% assign pageTags = [] %}{% if page.tags.first %}{% assign pageTags = page.tags %}{% endif %}
{% assign pageAttractions = [] %}{% if page.attractions.first %}{% assign pageAttractions = page.attractions %}{% endif %}
{% assign pageMembers = [] %}{% if page.members.first %}{% assign pageMembers = page.members %}{% endif %}
{% assign pageParks = [] %}{% if page.parks.first %}{% assign pageParks = page.parks %}{% endif %}
{% assign pageTagList = pageTags | concat: pageAttractions | concat: pageMembers | concat: pageParks %}
{% for post in site.documents %}
{% assign sameTagCount = 0 %}
{% assign commonTags = '' %}
{% assign postTags = [] %}{% if post.tags %}{% assign postTags = post.tags %}{% endif %}
{% assign postAttractions = [] %}{% if post.attractions %}{% assign postAttractions = post.attractions %}{% endif %}
{% assign postMembers = [] %}{% if post.members %}{% assign postMembers = post.members %}{% endif %}
{% assign postParks = [] %}{% if post.parks %}{% assign postParks = post.parks %}{% endif %}
{% assign postTageList = postTags | concat: postAttractions | concat: postMembers | concat postParks %}
{% if post.hidden == true %}
{% break %}
{% endif %}
{% for tag in postTageList %}
{% if post.url != page.url %}
{% if pageTagList contains tag %}
{% assign sameTagCount = sameTagCount | plus: 1 %}
{% capture tagmarkup %} <span class="label label-default">{{ tag }}</span> {% endcapture %}
{% assign commonTags = commonTags | append: tagmarkup %}
{% endif %}
{% endif %}
{% endfor %}
{% if sameTagCount >= minCommonTags %}
<li class="col-lg-4 col-md-12">
<div class="main-image">
</div>
<h5>{{ post.categories | first }}</h5>
<h3>{{ post.title | replace: 'Review', '' }}</h3>
<p>
{% if post.description %}
{{ post.description }}
{% else %}
{{ post.content | markdownify | strip_html | truncatewords: 20 }}
{% endif %}
</p>
<p>
Read Article →
</p>
</li>
{% assign maxRelatedCounter = maxRelatedCounter | plus: 1 %}
{% if maxRelatedCounter >= maxRelated %}
{% break %}
{% endif %}
{% endif %}
{% endfor %}
</ul>
You can see the repo here: https://github.com/dtsn/jungleskipper/blob/feature/members/_includes/related-posts.html
From the Liquid documentation:
You cannot initialize arrays using only Liquid.
You can, however, use the split filter to break a string into an array of substrings.
You should look at compact which removes any nil values from an array.
Here is a link to the doc on shopify.
Example from Liquid documentation
Input:
{% assign site_categories = site.pages | map: "category" %}
{% for category in site_categories %}
- {{ category }}
{% endfor %}
Output:
- business
- celebrities
-
- lifestyle
- sports
-
- technology
With Compact
Input:
{% assign site_categories = site.pages | map: "category" | compact %}
{% for category in site_categories %}
- {{ category }}
{% endfor %}
Output:
- business
- celebrities
- lifestyle
- sports
- technology

Paginate highly filtered Jekyll collection

I’m building an ICO Website. The website contains a ‘coins’ collection, this collection contains all the ico coin pages. I have then created pages such as https://moonlandingnetwork.netlify.com/upcomingico , https://moonlandingnetwork.netlify.com/activeico etc to display these coins based on whether they are upcoming, active or ended. I used this code to filter these coins based on the date and based on whether the coin is featured.
{% assign featuredcoins = site.coins | where: ‘ico.featured’, ‘true’ %}
{% assign regularcoins = site.coins | where: ‘ico.featured’, ‘false’ %}
{% assign timeframe = 86400 %}
{% assign current_date = “now” | date: “%s” | minus: timeframe %}
{% assign featuredUpcoming = “” | split: ‘,’ %}
{% for coin in featuredcoins %}
{% assign pre_ico_end_date = coin.pre-ico.end-date | date: “%s” | plus: 0 %}
{% assign ico_start_date = coin.ico-detail.start-date | date: “%s” | plus: 0 %}
{% if current_date > pre_ico_end_date and ico_start_date > current_date %}
{% assign featuredUpcoming = featuredUpcoming | push: coin %}
{% endif %}
{% endfor %}
{% assign regularUpcoming = “” | split: ‘,’ %}
{% for coin in regularcoins %}
{% assign pre_ico_end_date = coin.pre-ico.end-date | date: “%s” | plus: 0 %}
{% assign ico_start_date = coin.ico-detail.start-date | date: “%s” | plus: 0 %}
{% if current_date > pre_ico_end_date and ico_start_date > current_date %}
{% assign regularUpcoming = regularUpcoming | push: coin %}
{% endif %}
{% endfor %}
{% for coin in featuredUpcoming limit: 2 %}
<li>Featured Coin</li>
{% endfor %}
{% for coin in regularUpcoming %}
<li>Regular Coin</li>
{% endfor %}
Where I’m stuck is I need to paginate this. I know jekyll doesn’t support pagination of collections. I came across Jekyll paginate V2, Octopress Paginate and this code https://gist.github.com/Phlow/5613fb3f18946f577f071e2a258749a3 in my research. But i couldn’t get any of those to work.
I also require the pagination to work in such a way that the featured coins change on each new page along with the regular coins. Can somebody help me direct on what i should do.

Filter Collection Items by page metadata

Context
I have a jekyll collection called product-categories in which each file has the following metadata in front matter:
_product-categories/filename1.md
---
- title
- uuid
---
I have a page whose front matter contains filenames from this collection (collection array selections are saved by their filenames with front matter)...
page.html
---
product-categories:
- filename1
- filename2
---
[list of product-categories to be displayed here]
Goal
I want to display the title (from the collection metadata) of these product-categories on the page. Since the items are saved in the front matter by their filename, shouldn't this be possible?
You can do like this :
{% comment %} --- Get product-categories collection's datas --- {% endcomment %}
{% assign collection = site.collections | where: "label", "product-categories" | first %}
{% comment %} --- collection's docs root path --- {% endcomment %}
{% assign collection_path = collection.relative_directory | append: "/" %}
<ul>
{% for cat in page.product-categories %}
{% comment %} --- expected file path --- {% endcomment %}
{% assign filepath = collection_path | append: cat | append:".md" %}
{% comment %} Look for files that have path == filepath.
As "where" filter return an array,
we pick the first and only item in array {% endcomment %}
{% assign file = site.product-categories | where:"path", filepath | first %}
{% if file %}
<li>{{ file.title }}</li>
{% else %}
{% comment %} --- error in front matter list ---{% endcomment %}
<li>No file match for <strong>{{ cat }}</strong> : file at <strong>{{ filepath }}</strong> not found</li>
{% endif %}
{% endfor %}
</ul>

One variable for different collections in Jekyll to use in forloop

I have several collections on my Jekyll site. I've added post navigation to one of the collections displaying a counter on each post page:
{% assign testimonials = site.testimonials %}
{% assign page_order = 1 %}
{% for node in testimonials reversed %}
{% if node.url == page.url %}
{{ page_order }} from {{ forloop.length }}
{% else %}
{% assign page_order = page_order | plus: 1 %}
{% endif %}
{% endfor %}
I would like to make this code work not only for site.testimonials, but for other collections as well. I tried to pass a variable for collections like this:
{% capture label %}{{ page.collection }}{% endcapture %}
{% assign collection = site.collections | where: "label",label | first %}
{% for node in collection reversed %}
{% if node.url == page.url %}
{{ page_order }} from {{ forloop.length }}
{% else %}
{% assign page_order = page_order | plus: 1 %}
{% endif %}
{% endfor %}
But it doesn't work. Is there any way to pass a variable for all collections in Jekyll to use in forloop in post navigation?
When you access collection with site.testimonials, you get collection's documents array.
{{ site.testimonials | inspect }}
# output >> [#<Jekyll::Document ...>, #<Jekyll::Document ...>, ...]
When you access a collection while looping over site.collection, you receive the collection's object :
{% assign collection = site.collections | where: "label",page.collection | first %}
{{ collection | inspect }}
# output >> { "output": true, "label": "collectionLabel",
"docs": [ doc1, docs2, ... ], "files": [],
"directory": "/path/to/collection",
"relative_directory": "_colectionDirectory" }
In your case, you just have to replace :
{% for node in collection reversed %}
By :
{% for node in collection.docs reversed %}